Када сам креирао сопствену методологију за управљање резервним копијама на више МС-СКЛ сервера, провео сам доста времена проучавајући механизам за прослеђивање вредности у Поверсхелл-у током удаљених позива, тако да пишем себи подсетник у случају да је корисно неком другом.
Дакле, почнимо са једноставном скриптом и покренимо је локално:
$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. Шаг завершился с ошибкой.
Закључци:
- Избегавајте коришћење Врите-Оутпута и навођење израза без додељивања. Имајте на уму да премештање овог кода на друго место у скрипти може довести до неочекиваних резултата.
- У скриптама које нису намењене за ручно покретање, већ за коришћење у вашим механизмима аутоматизације, посебно за удаљене позиве преко ВИНРМ-а, урадите ручно руковање грешкама преко Три/Цатцх и обезбедите да, у било ком развоју догађаја, ова скрипта шаље тачно једну вредност примитивног типа . Ако желите да добијете класични ниво грешке, ова вредност мора бити нумеричка.
Извор: ввв.хабр.цом