Я хотел бы преобразовать реализацию Keith Hill С# в Get-Clipboard и Set-Clipboard в чистую PowerShell в качестве файла .PSM1.
Есть ли способ развернуть поток STA в PowerShell, как он это делает в своем Cmdlet при работе с буфером обмена?
Я хотел бы преобразовать реализацию Keith Hill С# в Get-Clipboard и Set-Clipboard в чистую PowerShell в качестве файла .PSM1.
Есть ли способ развернуть поток STA в PowerShell, как он это делает в своем Cmdlet при работе с буфером обмена?
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()
}
Попытка суммировать состояние дел и опций с 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): нет встроенных командлетов для взаимодействия с буфером обмена, но есть < сильные > обходные
Труба в стандартную утилиту командной строки 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 = New-Object System.Text.UnicodeEncoding $false, $false # UTF-16 encoding *without BOM*
Пример для тестирования (консоль PS отображает азиатские символы как "?", но все равно обрабатывает их правильно - например, проверьте содержимое буфера обмена в "Блокноте" ):
"I enjoyed Thomas Hübl talk about 中文" | clip # should appear as is on the clipboard
Примечание. Назначение $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
, по-видимому, понимает два формата:
clip.exe
всегда обрабатывает спецификацию как данные, следовательно, необходимо использовать кодировку без спецификации.Используйте решение PowerShell с прямым использованием классов .NET:
Обратите внимание, что доступ к буферу возможен только из потока в режиме STA (однопоточная квартира) - в отличие от MTA (многопоточная квартира):
powershell.exe
с помощью переключателя -mta
).powershell.exe
с помощью переключателя -sta
.PowerShell Core v6.0-rc2 (многоплатформенный) имеет не встроенных командлетов для взаимодействия с буфером обмена, даже если он работает Windows.
Ниже приведены "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:
Get-Clipboard
/Set-Clipboard
) вызываются за кулисами.System.Windows.Forms
; http://poshcode.org/2219 частично демонстрирует альтернативу WPF PresentationCore)
Set-ClipboardText
не может установить буфер обмена в пустую строку, поэтому вместо этого использует новую строку (и выдает предупреждение об этом).Запуск ядра PowerShell:
clip.exe
для установки текста и решения WSH/JScript для получения текста.pbcopy
и pbpaste
xclip
, , если они доступны и установлены; 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
всегда копирует спецификацию в буфер обмена).
Я просто писал, как это сделать:
http://www.nivot.org/2009/10/14/PowerShell20GettingAndSettingTextToAndFromTheClipboard.aspx
-Oisin
Сначала вы должны проверить свой хост. 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.
Взгляните на рецепт Lee Holme из Cookbook PowerShell: Set-Clipboard. Вы можете использовать это как Set-Clipboard.ps1 или просто отбросить код внутри функции PowerShell (вот пример из моего профиля PowerShell).
script позволит вам получить полный вывод в буфер обмена, например:
dir | Set-Clipboard
Я изначально узнал о решении Ли Холме из этого ответа.