Как начать новый сеанс PowerShell из другого сеанса PS и запустить CmdLet с разделенными параметрами

Цель:

Должен быть в состоянии проверить, установлен ли PowerShell v6 (что в порядке), и если это так, то вызвать эту оболочку для определенных CmdLets. Это будет вызываться в скрипте, запущенном в PowerShell v5.1. Я не могу полностью перейти на v6, так как есть другие зависимости, которые еще не работают в этой среде, однако v6 предлагает значительную оптимизацию для некоторых CmdLets, что приводит к улучшению работы более чем в 200 раз (в частности, Invoke-WebRequest где вызов приведет к загрузке большого файла - в версии 5.1 загрузка файла объемом 4 ГБ займет более 1 часа, в версии v это займет около 30 секунд на тех же компьютерах в той же подсети.

Дополнительные очки:

Однако я также создаю набор динамических параметров, которые используются для включения в список параметров CmdLets. Например, встроенный список параметров будет выглядеть примерно так:

$SplatParms = @{
    Method = "Get"
    Uri = $resource
    Credential = $Creds
    Body = (ConvertTo-Json $data)
    ContentType = "application/json"
}

И запуск CmdLet обычно будет работать как положено:

Invoke-RestMethod @SplatParms

Что было перепробовано:

За последние несколько дней я просмотрел различные посты на этом форуме и в других местах. Мы можем создать простой блок скрипта, который можно вызвать, и он также работает как ожидалось:

$ConsoleCommand = { Invoke-RestMethod @SplatParms }

& $ConsoleCommand

Тем не менее, пытаясь передать то же самое в Start-Process CmdLet терпит неудачу, поскольку я предполагаю, что хэш-таблица параметров не оценивается:

Start-Process pwsh -ArgumentList "-NoExit","-Command  &{$ConsoleCommand}" -wait

Результаты в:

Invoke-RestMethod : Cannot validate argument on parameter 'Uri'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
At line:1 char:22
+ &{ Invoke-RestMethod @SplatParms }

Где дальше?

Я предполагаю, что мне как-то нужно передать параметры в качестве аргументов, чтобы их потом можно было оценить и выделить, однако синтаксис ускользает от меня. Я даже не уверен, если Start-Process является лучшим CmdLet для использования, но лучше я должен смотреть на что-то еще, как Invoke-Commandили что то совсем другое?

Было бы здорово вернуть результат этого CmdLet обратно в исходную оболочку, но в данный момент он просто возьмет что-то, что функционирует.

3 ответа

Примечание. В принципе, методы, описанные в этом ответе, могут применяться не только к вызовам из Windows PowerShell в PowerShell Core, но и в противоположном направлении, а также между экземплярами одной и той же версии PowerShell, как в Windows, так и в Unix.

Вам не нужно Start-Process; Вы можете призвать pwsh напрямую, с помощью блока скрипта:

pwsh -c { $SplatParms = $Args[0]; Invoke-RestMethod @SplatParms } -args $SplatParms

Обратите внимание на необходимость передачи хеш-таблицы в качестве аргумента, а не как части блока скрипта.

  • К сожалению, в Windows PowerShell 5.1 / PowerShell Core 6.0.0 существует проблема с передачей [PSCredential] примеры этого способа - см. нижнюю часть для обходного пути.

Это будет выполнено синхронно и даже напечатает вывод из блока скрипта в консоли.

Предостережение заключается в том, что захват такого вывода - путем присвоения переменной или перенаправления в файл - может завершиться неудачей, если будут возвращены экземпляры типов, которые недоступны в вызывающем сеансе.

В качестве неоптимального обходного пути вы можете использовать -o Text (-OutputFormat Text) спасибо, PetSerAl, чтобы захватить вывод в виде текста, точно так же, как он будет выводить на консоль (запустить pwsh -h чтобы увидеть все варианты).

Вывод по умолчанию возвращается в сериализованном формате CLIXML, и вызывающие сеансы PowerShell десериализуют его обратно в объекты. Если тип сериализованного объекта не распознан, возникает ошибка.

Простой пример (выполнить из Windows PowerShell):

# This FAILS, but you can add `-o text` to capture the output as text.
WinPS> $output = pwsh -c { $PSVersionTable.PSVersion } # !! FAILS
pwsh : Cannot process the XML from the 'Output' stream of 'C:\Program Files\PowerShell\6.0.0\pwsh.exe': 
SemanticVersion XML tag is not recognized. Line 1, position 82.
...

Это не удается, потому что $PSVersionTable.PSVersion имеет тип [System.Management.Automation.SemanticVersion] в PowerShell Core, который недоступен в Windows PowerShell начиная с версии 5.1 (в Windows PowerShell тип того же свойства [System.Version]).


Обходной путь для неспособности передать [PSCredential] пример:

pwsh -c { 
          $SplatParms = $Args[0];
          $SplatParams.Credential = [pscredential] $SplatParams.Credential;
          Invoke-RestMethod @SplatParms
        } -args $SplatParms

Вызов другого экземпляра PowerShell из PowerShell с использованием блока сценариев включает сериализацию и десериализацию объектов в формате CLIXML, что также используется в удаленном взаимодействии PowerShell.

Как правило, существует много типов.NET, которые десериализация не может точно воссоздать и в таких случаях создает [PSCustomObject] экземпляры, эмулирующие экземпляры исходного типа, с (обычно скрытыми) .pstypenames свойство, отражающее исходное имя типа с префиксом Deserialized.

Начиная с Windows PowerShell 5.1 / PowerShell Core 6.0.0, это также происходит с экземплярами [pscredential] ([System.Management.Automation.PSCredential]), что предотвращает их прямое использование в целевой сессии - см. эту проблему GitHub.

К счастью, однако, просто приведение десериализованного объекта обратно к [pscredential] похоже на работу.

Попробуйте создать New-PSSession против 6.0 в вашей 5.1-сессии.

После установки powershell core 6.0 и запуска Enable-PSRemoting, новая конфигурация PSSessionConfiguration была создана для 6.0:

PS > Get-PSSessionConfiguration


Name          : microsoft.powershell
PSVersion     : 5.1
StartupScript : 
RunAsUser     : 
Permission    : NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed

Name          : microsoft.powershell.workflow
PSVersion     : 5.1
StartupScript : 
RunAsUser     : 
Permission    : BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed

Name          : microsoft.powershell32
PSVersion     : 5.1
StartupScript : 
RunAsUser     : 
Permission    : NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed

Name          : PowerShell.v6.0.0
PSVersion     : 6.0
StartupScript : 
RunAsUser     : 
Permission    : NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed

В родительском скрипте создайте новый сеанс, используя имя конфигурации 6.0 PowerShell.v6.0.0 и передать его любому последующему Invoke-Command вам требуется. Результаты возвращаются как объекты. Scriptblocks может потребовать локальных переменных, переданных через -ArgumentListза ответ mklement0.

$ps6sess = New-PSSession -ComputerName localhost -ConfigurationName 'PowerShell.v6.0.0'
$results = Invoke-Command -Session $ps60sess -ScriptBlock {Param($splatthis) Invoke-WebRequest @splatthis} -ArgumentList $SplatParms

Также может быть полезно знать, что сеанс сохраняется между вызовами Invoke-Command. Например, любые новые переменные, которые вы создадите, будут доступны при последующих вызовах в этом сеансе:

PS > Invoke-Command -Session $ps60sess -ScriptBlock {$something = 'zers'}

PS > Invoke-Command -Session $ps60sess -ScriptBlock {write-host $something }
zers

Проблемы с передачей PSCredential, похоже, не являются проблемой при таком подходе:

$ps6sess = New-PSSession -ComputerName localhost -ConfigurationName 'PowerShell.v6.0.0'
$credential = Get-Credential -UserName 'TestUser'

$IRestArgs = @{
    Method='GET'
    URI = 'https://httpbin.org'
    Credential = $credential
}
$IRestBlock = {Param($splatval) Invoke-RestMethod @splatval}
Invoke-Command -Session $ps6sess -ScriptBlock $IRestBlock -ArgumentList $IRestArgs
# no error

pwsh -c { 
      Param ($SplatParms)
      #$SplatParams.Credential = [pscredential] $SplatParams.Credential;
      Invoke-RestMethod @SplatParms
} -args $IRestArgs
# error - pwsh : cannot process argument transformation on 
#   parameter 'Credential. username

Возможно, на сеансе PS6 знает, что получает блок от PS5.1, и знает, как разместить.

Немедленная флэш-память, использующая процесс запуска, не является подходящей, поэтому придется исследовать ее, независимо от того, моя реакция состоит в том, чтобы создать массив параметров, чтобы использовать их без пробелов.

Другие вопросы по тегам