Подтвердить что ты не робот

Преобразование Keith Hill PowerShell Get-Clipboard и Set-Clipboard в PSM1 script

Я хотел бы преобразовать реализацию Keith Hill С# в Get-Clipboard и Set-Clipboard в чистую PowerShell в качестве файла .PSM1.

Есть ли способ развернуть поток STA в PowerShell, как он это делает в своем Cmdlet при работе с буфером обмена?

Сообщение в блоге
Код

4b9b3361

Ответ 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 = 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, по-видимому, понимает два формата:

        • системная текущая кодовая страница OEM (например, IBM 437)
        • UTF-16 LE ( "Юникод" )
        • К сожалению, clip.exe всегда обрабатывает спецификацию как данные, следовательно, необходимо использовать кодировку без спецификации.
        • Обратите внимание, что приведенные выше кодировки имеют значение только для правильного обнаружения ввода; один раз в буфере обмена входная строка доступна во всех следующих кодировках: UTF-16 LE, "ANSI" и OEM.
    • Используйте решение PowerShell с прямым использованием классов .NET:

      • Обратите внимание, что доступ к буферу возможен только из потока в режиме STA (однопоточная квартира) - в отличие от MTA (многопоточная квартира):

        • v3: STA по умолчанию (режим MTA может быть введен путем вызова powershell.exe с помощью переключателя -mta).
        • v2 и v1: MTA по умолчанию; Режим STA можно ввести, вызвав powershell.exe с помощью переключателя -sta.
        • Ergo: надежные функции должны иметь доступ к буферу из сеансов в любом режиме.
  • 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 всегда копирует спецификацию в буфер обмена).

Ответ 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

Я изначально узнал о решении Ли Холме из этого ответа.