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