Враћање вредности из поверсхелл инвоке-цомманд СКЛ Сервер агенту

Када сам креирао сопствену методологију за управљање резервним копијама на више МС-СКЛ сервера, провео сам доста времена проучавајући механизам за прослеђивање вредности ​​​у Поверсхелл-у током удаљених позива, тако да пишем себи подсетник у случају да је корисно неком другом.

Дакле, почнимо са једноставном скриптом и покренимо је локално:

$exitcode = $args[0]
Write-Host 'Out to host.'
Write-Output 'Out to output.'
Write-Host ('ExitCode: ' + $exitcode)
Write-Output $exitcode
$host.SetShouldExit($exitcode)

Да бих покренуо скрипте, користићу следећу ЦМД датотеку, нећу је укључивати сваки пут:

@Echo OFF
PowerShell .TestOutput1.ps1 1
ECHO ERRORLEVEL=%ERRORLEVEL%

На екрану ћемо видети следеће:

Out to host.
Out to output.
ExitCode: 1
1
ERRORLEVEL=1


Хајде сада да покренемо исту скрипту преко ВСМАН-а (даљински):

Invoke-Command -ComputerName . -ScriptBlock { &'D:sqlagentTestOutput1.ps1' $args[0] } -ArgumentList $args[0]

А ево и резултата за вас:

Out to host.
Out to output.
ExitCode: 2
2
ERRORLEVEL=0

Одлично, ниво грешке је негде нестао, али морамо да добијемо вредност из скрипте! Хајде да пробамо следећи дизајн:

$res=Invoke-Command -ComputerName . -ScriptBlock { &'D:sqlagentTestOutput1.ps1' $args[0] } -ArgumentList $args[0]

Ово је још интересантније. Порука у излазу је негде нестала:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Сада, као лирску дигресију, приметићу да ако унутар функције Поверсхелл напишете Врите-Оутпут или само израз без додељивања било којој променљивој (а ово имплицитно имплицира излаз на излазни канал), онда чак и када се покреће локално, ништа се неће приказати на екрану! Ово је последица архитектуре поверсхелл цевовода – свака функција има свој излазни цевовод, за њу се креира низ, а све што улази у њега сматра се резултатом извршавања функције, оператор Ретурн додаје повратну вредност истој пипелине као последњи елемент и преноси контролу на функцију која позива. За илустрацију, покренимо следећу скрипту локално:

Function Write-Log {
  Param( [Parameter(Mandatory=$false, ValueFromPipeline=$true)] [String[]] $OutString = "`r`n" )
  Write-Output ("Function: "+$OutString)
  Return "ReturnValue"
}
Write-Output ("Main: "+"ParameterValue")
$res = Write-Log "ParameterValue"
$res.GetType()
$res.Length
$res | Foreach-Object { Write-Host ("Main: "+$_) }

И ево резултата:

Main: ParameterValue

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array
2
Main: Function: ParameterValue
Main: ReturnValue

Главна функција (тело скрипте) такође има свој излазни цевовод, и ако покренемо прву скрипту из ЦМД-а, преусмеравајући излаз у датотеку,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

онда ћемо видети на екрану

ERRORLEVEL=1

и у досијеу

Out to host.
Out to output.
ExitCode: 1
1

ако упутимо сличан позив из поверсхелл-а

PS D:sqlagent> .TestOutput1.ps1 1 > TestOutput1.txt

онда ће бити на екрану

Out to host.
ExitCode: 1

и у досијеу

Out to output.
1

Ово се дешава зато што ЦМД покреће поверсхелл, који, у недостатку других инструкција, меша две нити (Хост и Оутпут) и даје их ЦМД-у, који шаље све што је примио у датотеку, а у случају покретања из поверсхелл-а, ове две нити постоје одвојено, а преусмеравања симбола утичу само на излаз.

Враћајући се на главну тему, подсетимо се да .НЕТ објектни модел унутар поверсхелл-а у потпуности постоји у оквиру једног рачунара (један ОС), када се код даљински покреће преко ВСМАН-а, пренос објеката се одвија кроз КСМЛ серијализацију, што доноси много додатног интересовања нашем истраживању. Хајде да наставимо наше експерименте покретањем следећег кода:

$res=Invoke-Command -ComputerName . -ScriptBlock { &'D:sqlagentTestOutput1.ps1' $args[0] } -ArgumentList $args[0]
$res.GetType()
$host.SetShouldExit($res)

А ево шта имамо на екрану:

Out to host.

ExitCode: 3

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array
Не удается преобразовать аргумент "exitCode", со значением: "System.Object[]", для "SetShouldExit" в тип "System.Int32": "Не удается преобразовать значение "System.Object[]" типа "System.Object[]" в тип "System
.Int32"."
D:sqlagentTestOutput3.ps1:3 знак:1
+ $host.SetShouldExit($res)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodException
    + FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument

ERRORLEVEL=0

Сјајан резултат! То значи да се приликом позивања Инвоке-Цомманд-а одржава подела цевовода на две нити (Хост и Оутпут), што нам даје наду у успех. Покушајмо да оставимо само једну вредност у излазном току, за коју ћемо променити прву скрипту коју покрећемо на даљину:

$exitcode = $args[0]
Write-Host 'Out to host.'
#Write-Output 'Out to output.'
Write-Host ('ExitCode: ' + $exitcode)
Write-Output $exitcode
$host.SetShouldExit($exitcode)

Хајде да га покренемо овако:

$res=Invoke-Command -ComputerName . -ScriptBlock { &'D:sqlagentTestOutput1.ps1' $args[0] } -ArgumentList $args[0]
$host.SetShouldExit($res)

и... ДА, изгледа као победа!

Out to host.
ExitCode: 4

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Int32                                    System.ValueType


ERRORLEVEL=4

Хајде да покушамо да схватимо шта се догодило. Локално смо позвали поверсхелл, који је заузврат позвао поверсхелл на удаљеном рачунару и тамо извршио нашу скрипту. Два тока (Хост и Оутпут) са удаљене машине су серијализована и прослеђена назад, док је излазни ток, који има једну дигиталну вредност у себи, конвертован у тип Инт32 и као такав прослеђен на страну која прима, а страна која га је користила као излазни код поверсхелл-а позиваоца.

И као коначну проверу, хајде да направимо посао у једном кораку на СКЛ серверу са типом „Оперативни систем (цмдекец)“ са следећим текстом:

PowerShell -NonInteractive -NoProfile "$res=Invoke-Command -ComputerName BACKUPSERVER -ConfigurationName SQLAgent -ScriptBlock {&'D:sqlagentTestOutput1.ps1' 6}; $host.SetShouldExit($res)"

УРА! Задатак је завршен са грешком, текст у дневнику:

Выполняется от имени пользователя: DOMAINagentuser. Out to host. ExitCode: 6.  Код завершения процесса 6.  Шаг завершился с ошибкой.

Закључци:

  • Избегавајте коришћење Врите-Оутпута и навођење израза без додељивања. Имајте на уму да премештање овог кода на друго место у скрипти може довести до неочекиваних резултата.
  • У скриптама које нису намењене за ручно покретање, већ за коришћење у вашим механизмима аутоматизације, посебно за удаљене позиве преко ВИНРМ-а, урадите ручно руковање грешкама преко Три/Цатцх и обезбедите да, у било ком развоју догађаја, ова скрипта шаље тачно једну вредност примитивног типа . Ако желите да добијете класични ниво грешке, ова вредност мора бити нумеричка.

Извор: ввв.хабр.цом

Додај коментар