Преобразование Keith Hill PowerShell Get-Clipboard и Set-Clipboard в PSM1 script
Я хотел бы преобразовать реализацию Keith Hill С# в Get-Clipboard и Set-Clipboard в чистую PowerShell в качестве файла .PSM1.
Есть ли способ развернуть поток STA в PowerShell, как он это делает в своем Cmdlet при работе с буфером обмена?
Сообщение в блоге
Код
Ответы
Ответ 1
TextBox не требует переключения -STA.
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()
}
Ответ 2
Попытка суммировать состояние дел и опций с Windows PowerShell v5.1/PowerShell Core v6.0-rc.2:
-
Windows PowerShell v5.1 +: используйте встроенный Get-Clipboard
и Set-Clipboard
.
-
Windows PowerShell v5.0 - (v1 - v5.0): нет встроенных командлетов для взаимодействия с буфером обмена, но есть < сильные > обходные
- Используйте Расширения сообщества PowerShell (PSCX; http://pscx.codeplex.com/), которые поставляются с несколькими командлетами, связанными с буфером обмена что выходит за рамки просто обработки текста.
-
Труба в стандартную утилиту командной строки clip.exe
(W2K3 + серверная, клиентская сторона Vista +) [1]:
-
Примечание. Помимо проблем с кодированием, рассмотренных ниже, ... | clip.exe
неизменно добавляет завершающую новую строку к входу; единственный способ избежать этого - использовать временный файл, содержимое которого предоставляется через перенаправление ввода cmd <
- см. ниже Set-ClipboardText
.
-
Если требуется поддержка только ASCII-символа (7-разрядная): работает по умолчанию.
-
Если требуется поддержка OEM-кодирования (8-разрядная) (например, IBM437 в США), выполните следующее:
-
$OutputEncoding = [System.Text.Encoding]::GetEncoding([System.Globalization.CultureInfo]::CurrentCulture.TextInfo.OEMCodePage)
-
Если требуется полная поддержка Unicode, необходимо использовать кодировку UTF-16 LE без спецификации; выполните следующие действия:
-
Примечание. Назначение $OutputEncoding
, как указано выше, отлично работает в глобальной области, но не в противном случае, например, в функции из-за ошибки с Windows PowerShell v5.1/PowerShell Core v6.0.0- rc.2 - см. https://github.com/PowerShell/PowerShell/issues/5763
- В неглобальном контексте используйте
(New-Object ...).psobject.BaseObject
, чтобы обойти ошибку, или - в PSv5 + - вместо этого используйте [...]:new()
.
-
Примечание: clip.exe
, по-видимому, понимает два формата:
- системная текущая кодовая страница OEM (например, IBM 437)
- UTF-16 LE ( "Юникод" )
- К сожалению,
clip.exe
всегда обрабатывает спецификацию как данные, следовательно, необходимо использовать кодировку без спецификации.
- Обратите внимание, что приведенные выше кодировки имеют значение только для правильного обнаружения ввода; один раз в буфере обмена входная строка доступна во всех следующих кодировках: UTF-16 LE, "ANSI" и OEM.
-
Используйте решение PowerShell с прямым использованием классов .NET:
-
PowerShell Core v6.0-rc2 (многоплатформенный) имеет не встроенных командлетов для взаимодействия с буфером обмена, даже если он работает Windows.
- Обходной путь заключается в использовании утилит или API-интерфейсов конкретной платформы - см. ниже.
Ниже приведены "polyfill" функции Get-ClipboardText
и Set-ClipboardText
для получения и установки текста из буфера обмена; они работают с Windows PowerShell v2 +, а также PowerShell Core (с ограничениями, см. ниже).
-
Примечание. Строго говоря, функции не являются полиполками, учитывая, что их имена отличаются от встроенных командлетов. Тем не менее, суффикс имени Text был выбран так, чтобы он явно указывал, что эти функции обрабатывают только текст.
-
Код с благодарностью основывается на информации с разных сайтов, в частности на @hoge answer (fooobar.com/questions/274075/...) и http://techibee.com/powershell/powershell-script-to-copy-powershell-command-output-to-clipboard/1316
-
Работа в Windows PowerShell:
- В v5.1 + встроенные командлеты (
Get-Clipboard
/Set-Clipboard
) вызываются за кулисами.
- В более ранних версиях функции работают как в режиме MTA, так и в режиме STA, и использовать более эффективный код в режиме STA (код основан на
System.Windows.Forms
; http://poshcode.org/2219 частично демонстрирует альтернативу WPF PresentationCore)
- Caveat. В Windows PowerShell в режиме MTA
Set-ClipboardText
не может установить буфер обмена в пустую строку, поэтому вместо этого использует новую строку (и выдает предупреждение об этом).
-
Запуск ядра PowerShell:
- Нативные утилиты называются за кулисами:
- Windows:
clip.exe
для установки текста и решения WSH/JScript для получения текста.
- macOS:
pbcopy
и pbpaste
- Linux:
xclip
, , если они доступны и установлены;
например, на Ubuntu, используйте sudo apt-get xclip
для установки.
-
Set-ClipboardText
может принимать любые типы объектов в качестве входных (которые затем преобразуются в текст) либо напрямую, либо из конвейера.
-
Вызовите -Verbose
, чтобы узнать, какая техника используется за кулисами для доступа к буферу.
Get-ClipboardText
function Get-ClipboardText {
[CmdletBinding()] # to support -OutVariable and -Verbose
param()
if ($PSVersionTable.PSEdition -eq 'Desktop') { # *Windows* PowerShell
if ($PSVersionTable.PSVersion -ge [version] '5.1.0') { # Ps*Win* v5.1+ now has Get-Clipboard / Set-Clipboard cmdlets.
Get-Clipboard -Format Text
} else {
Add-Type -AssemblyName System.Windows.Forms
if ([threading.thread]::CurrentThread.ApartmentState.ToString() -eq 'STA') {
# -- STA mode:
Write-Verbose "STA mode: Using [Windows.Forms.Clipboard] directly."
# To be safe, we explicitly specify that Unicode (UTF-16) be used - older platforms may default to ANSI.
[System.Windows.Forms.Clipboard]::GetText([System.Windows.Forms.TextDataFormat]::UnicodeText)
} else {
# -- MTA mode: Since the clipboard must be accessed in STA mode, we use a [System.Windows.Forms.TextBox] instance to mediate.
Write-Verbose "MTA mode: Using a [System.Windows.Forms.TextBox] instance for clipboard access."
$tb = New-Object System.Windows.Forms.TextBox
$tb.Multiline = $tru
$tb.Paste()
$tb.Text
}
}
} else { # PowerShell Core
if ($env:OS -eq 'Windows_NT') {
# Gratefully adapted from http://stackoverflow.com/a/15747067/45375
# Note that trying the following directly from PowerShell Core does NOT work,
# (New-Object -ComObject htmlfile).parentWindow.clipboardData.getData('text')
# because .parentWindow is always $null
$tempFile = [io.path]::GetTempFileName()
"WSH.Echo(WSH.CreateObject('htmlfile').parentWindow.clipboardData.getData('text'));" | set-content $tempFile
cscript /nologo /e:JScript $tempFile
Remove-Item $tempFile
} elseif ((uname) -eq 'Darwin') {
pbpaste
} else {
# Note: May work on Ubuntu only, and there only if xclip was
# installed with: sudo apt install xclip
xclip -sel clipboard -o
}
}
}
Set-ClipboardText
function Set-ClipboardText() {
[CmdletBinding()]
Param(
[Parameter(Mandatory, ValueFromPipeline)]
[ValidateNotNull()]
$InputObject
)
# Each input object is converted to a string representation with Out-String,
# unless it already is a string; the representations of multiple input objects
# are separated - but not terminated - with a platform-native newline.
$allText = $sep = ''
# $Input having a value means that pipeline input was provided.
# Note: Since we want to access all pipeline input *at once*,
# we do NOT use a process {} block.
if ($Input) { $InputObject = $Input }
foreach ($o in $InputObject) {
$text = if ($o -is [string]) { $o } else { $o | Out-String }
$allText += $sep + $text
if (-not $sep) { $sep = [Environment]::NewLine }
}
if ($PSVersionTable.PSEdition -eq 'Desktop') { # *Windows* PowerShell
if ($PSVersionTable.PSVersion -ge [version] '5.1.0') { # Ps*Win* v5.1+ now has Get-Clipboard / Set-Clipboard cmdlets.
Set-Clipboard -Value $allText
} else {
Add-Type -AssemblyName System.Windows.Forms
if ([threading.thread]::CurrentThread.ApartmentState.ToString() -eq 'STA') {
# -- STA mode: we can use [Windows.Forms.Clipboard] directly.
Write-Verbose "STA mode: Using [Windows.Forms.Clipboard] directly."
if ($allText.Length -eq 0) { $AllText = "`0" } # Strangely, SetText() breaks with an empty string, claiming $null was passed -> use a null char.
# To be safe, we explicitly specify that Unicode (UTF-16) be used - older platforms may default to ANSI.
[System.Windows.Forms.Clipboard]::SetText($allText, [System.Windows.Forms.TextDataFormat]::UnicodeText)
} else {
# -- MTA mode: Since the clipboard must be accessed in STA mode, we use a [System.Windows.Forms.TextBox] instance to mediate.
Write-Verbose "MTA mode: Using a [System.Windows.Forms.TextBox] instance for clipboard access."
if ($allText.Length -eq 0) {
# !! This approach cannot set the clipboard to an empty string: the text box must
# !! must be *non-empty* in order to copy something. A null character doesn't work.
# !! We use the least obtrusive alternative - a newline - and issue a warning.
$allText = "`r`n"
Write-Warning "Setting clipboard to empty string not supported in MTA mode; using newline instead."
}
$tb = New-Object System.Windows.Forms.TextBox
$tb.Multiline = $true
$tb.Text = $allText
$tb.SelectAll()
$tb.Copy()
}
}
} else { # PowerShell *Core*
# No native PS support for writing to the clipboard ->
# external utilities must be used.
# To prevent adding a trailing \n, which PS inevitably adds when sending
# a string through the pipeline to an external command, use a temp. file,
# whose content can be provided via native input redirection (<)
$tmpFile = [io.path]::GetTempFileName()
# Determine the encoding: Unix platforms need UTF8, whereas
# Windows clip.exe needs UTF-16LE - both in *BOM-less* form.
if ($env:OS -eq 'Windows_NT') {
# clip.exe on Windows only works as expected with non-ASCII characters
# with UTF-16LE encoding.
# Unfortunately, it invariably treats the BOM as *data* too, so
# we cannot use 'Set-Content -Enocding Unicode' and must use a
# BOM-less encoding via the .NET Framework.
[io.file]::WriteAllText($tmpFile, $allText, [System.Text.UnicodeEncoding]::new($false, $false))
} else {
# PowerShell UTF8 encoding invariably creates a file WITH BOM
# so we use the .NET Framework, whose default is BOM-*less* UTF8.
[IO.File]::WriteAllText($tmpFile, $allText)
}
if ($env:OS -eq 'Windows_NT') {
Write-Verbose "Windows: using clip.exe"
cmd /c clip.exe '<' $tmpFile
} elseif ((uname) -eq 'Darwin') {
Write-Verbose "macOS: Using pbcopy."
bash -c "pbcopy < '$tmpFile'"
} else {
Write-Verbose "Linux: trying xclip -sel clip"
bash -c "xclip -sel clip < '$tmpFile'"
}
Remove-Item $tmpFile
}
}
[1] В более ранней версии этого ответа неверно утверждалось, что clip.exe
:
- всегда добавляет разрыв строки при копировании в буфер обмена (это НЕ)
- правильно обрабатывает UTF-16 LE BOM в файлах, перенаправленных на stdin через <
, а также при вводе данных через |
(clip.exe
всегда копирует спецификацию в буфер обмена).
Ответ 3
Я просто писал, как это сделать:
http://www.nivot.org/2009/10/14/PowerShell20GettingAndSettingTextToAndFromTheClipboard.aspx
-Oisin
Ответ 4
Сначала вы должны проверить свой хост. ISE уже запускает STA, поэтому нет необходимости разворачивать другой поток или оболочку (что является оптимизацией, которая включена в мой список задач для PSCX). Для консольной подсказки, которая является MTA, я бы выложил ее в двоичный код либо, как показывает Oisin, либо использовал простое небольшое приложение С#, например:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
class OutClipboard {
[STAThread]
static void Main() {
Clipboard.SetText(Console.In.ReadToEnd());
}
}
И для получения содержимого буфера обмена Vista и более поздние версии имеют clip.exe.
Я не думаю, что даже 2.0 расширенные функции готовы разрешить людям со своими потоками .NET в script.
Ответ 5
Взгляните на рецепт Lee Holme из Cookbook PowerShell: Set-Clipboard. Вы можете использовать это как Set-Clipboard.ps1 или просто отбросить код внутри функции PowerShell (вот пример из моего профиля PowerShell).
script позволит вам получить полный вывод в буфер обмена, например:
dir | Set-Clipboard
Я изначально узнал о решении Ли Холме из этого ответа.