开发者

Convert Keith Hill's PowerShell Get-Clipboard and Set-Clipboard to a PSM1 script

I'd like to convert Keith Hill's C# implem开发者_如何学JAVAentation 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:

        • $OutputEncoding = New-Object System.Text.UnicodeEncoding $false, $false # UTF-16 encoding *without BOM*
        • Example to test with (the PS console will display the Asian chars. as "??", but still handle them correctly - verify clipboard content in Notepad, for instance):

          • "I enjoyed Thomas Hübl's talk about 中文" | clip # should appear as is on the clipboard
      • 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:

      • Note that clipboard access can only occur from a thread in STA (single-threaded apartment) mode - as opposed to MTA (multi-threaded apartment):

        • v3: STA is the default (MTA mode can be entered by invoking powershell.exe with the -mta switch).
        • v2 and v1: MTA is the default; STA mode can be entered by invoking powershell.exe with the -sta switch.
        • Ergo: Robust functions should be able to access the clipboard from sessions in either mode.
  • 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.


In PowerShell 5, we now have Get-Clipboard and Set-Clipboard.

On Windows Server 2012 R2, where we only have PowerShell 4, I was able to use .NET to manipulate the clipboard.

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

$copied = [System.Windows.Forms.Clipboard]::GetText()

$to_paste = 'Hello World'
[System.Windows.Forms.Clipboard]::SetText($to_paste)
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜