I'd like to convert Keith Hill's C# implementation of Get-Clipboard and Set-Clipboard into pure PowerShell as a .PSM1 file.
Is there a way to spin up an STA thread in PowerShell as he does in his Cmdlet when working with the clipboard?
The Blog Post
The Code
TextBox doesn't require -STA switch.
function Get-ClipBoard {
Add-Type -AssemblyName System.Windows.Forms
$tb = New-Object System.Windows.Forms.TextBox
$tb.Multiline = $true
$tb.Paste()
$tb.Text
}
function Set-ClipBoard() {
Param(
[Parameter(ValueFromPipeline=$true)]
[string] $text
)
Add-Type -AssemblyName System.Windows.Forms
$tb = New-Object System.Windows.Forms.TextBox
$tb.Multiline = $true
$tb.Text = $text
$tb.SelectAll()
$tb.Copy()
}
See the bottom section for a cross-edition, cross-platform module that offers clipboard text support in PowerShell Core and in Windows PowerShell v2 - v4.
An attempt to summarize the state of affairs and options as of Windows PowerShell v5.1 / PowerShell Core v6.1.0:
Windows PowerShell v5.0+: Use the built-in Get-Clipboard
and Set-Clipboard
cmdlets.
Windows PowerShell v4.0- (v1 - v4.0): has no built-in cmdlets for interacting with the clipboard, but there are workarounds:
- Use PowerShell Community Extensions (PSCX; http://pscx.codeplex.com/), which come with several clipboard-related cmdlets that go beyond just handling text.
Pipe to the standard command-line utility clip.exe
(W2K3+ server-side, Vista+ client-side)[1]:
Note: Aside from the encoding issues discussed below, ... | clip.exe
invariably appends a trailing newline to the input; the only way to avoid that is to use a temporary file whose content is provided via cmd's <
input redirection - see the Set-ClipboardText
function below.
If only ASCII-character (7-bit) support is needed: works by default.
If only OEM-encoding (8-bit) support (e.g., IBM437 in the US) is needed, run the following first:
$OutputEncoding = [System.Text.Encoding]::GetEncoding([System.Globalization.CultureInfo]::CurrentCulture.TextInfo.OEMCodePage)
If full Unicode support is needed, a UTF-16 LE encoding without BOM must be used; run the following first:
Note: Assigning to $OutputEncoding
as above works fine in the global scope, but not otherwise, such as in a function, due to a bug as of Windows PowerShell v5.1 / PowerShell Core v6.0.0-rc.2 - see https://github.com/PowerShell/PowerShell/issues/5763
- In a non-global context, use
(New-Object ...).psobject.BaseObject
to work around the bug, or - in PSv5+ - use [...]:new()
instead.
Note: clip.exe
apparently understands 2 formats:
- the system's current OEM codepage (e.g., IBM 437)
- UTF-16 LE ("Unicode")
- Unfortunately,
clip.exe
always treats a BOM as data, hence the need to use a BOM-less encoding.
- Note that the above encodings matter only with respect to correctly detecting input; once on the clipboard, the input string is available in all of the following encodings: UTF-16 LE, "ANSI", and OEM.
Use a PowerShell-based solution with direct use of .NET classes:
PowerShell Core (multi-platform), as of v6.1.0, has no built-in cmdlets for interacting with the clipboard, not even when run on Windows.
- The workaround is to use platform-specific utilities or APIs - see below.
My ClipboardText
module provides
"polyfill" functions Get-ClipboardText
and Set-ClipboardText
for getting and setting text from the clipboard; they work on Windows PowerShell v2+ as well as on PowerShell Core (with limitations, see below).
In the simplest case (PSv5+ or v3/v4 with the package-management modules installed), you can install it from the PowerShell Gallery from an elevated / sudo
session as follows:
Install-Module ClipboardText
For more information, including prerequisites and manual-installation instructions, see the repo.
Note: Strictly speaking, the functions aren't polyfills, given that their names differ from the built-in cmdlets. However, the name suffix Text was chosen so as to make it explicit that these functions handle text only.
The code gratefully builds on information from various sites, notably @hoge's answer (https://stackoverflow.com/a/1573295/45375) and http://techibee.com/powershell/powershell-script-to-copy-powershell-command-output-to-clipboard/1316
Running on Windows PowerShell v5+ in STA mode:
- The built-in cmdlets (
Get-Clipboard
/ Set-Clipboard
) are called behind the scenes.
Note that STA mode (a COM threading model) is the default since v3, but you can opt into MTA (multi-threaded mode) with command-line option -MTA
.
In all other cases (Windows PowerShell v4- and/or in MTA mode, PowerShell Core on all supported platforms):
- Windows:
- A P/Invoke-based solution that calls the Windows API is used, via ad-hoc C# code compiled with
Add-Type
.
- Unix-like platforms: Native utilities are called behind the scenes:
- macOS:
pbcopy
and pbpaste
- Linux:
xclip
, if available and installed;
for instance, on Ubuntu, use sudo apt-get xclip
to install.
Set-ClipboardText
can accept any type of object(s) as input (which is/are then converted to text the same way they would render in the console), either directly, or from the pipeline.
Invoke with -Verbose
to see what technique is used behind the scenes to access the clipboard.
[1] An earlier version of this answer incorrectly claimed that clip.exe
:
- always appends a line break when copying to the clipboard (it does NOT)
- correctly handles UTF-16 LE BOMs in files redirected to stdin via <
vs. when input is piped via |
(clip.exe
always copies the BOM to the clipboard, too).
I just blogged how to do this:
http://www.nivot.org/2009/10/14/PowerShell20GettingAndSettingTextToAndFromTheClipboard.aspx
-Oisin
You should check your host first. ISE already runs STA so there is no need to spin up another thread or shell out (which is an optimization that's on my todo list for PSCX). For the console prompt, which is MTA, then I would shell out to binary code either as Oisin shows or use a simple little C# app like:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
class OutClipboard {
[STAThread]
static void Main() {
Clipboard.SetText(Console.In.ReadToEnd());
}
}
And for getting the clipboard contents, Vista and later have clip.exe.
I don't think that even 2.0's advanced functions is ready to let folks party with their own .NET threads in a script.
Take a look at Lee Holme's recipe from the PowerShell Cookbook: Set-Clipboard. You can use at as Set-Clipboard.ps1, or just drop the code inside a PowerShell function (here's an example from my PowerShell profile).
The script will allow you to get the full piped output to the clipboard, e.g.:
dir | Set-Clipboard
I originally learned of Lee Holme's solution from this answer.