Враќање вредност од командата за повикување powershell на агентот на SQL Server

Кога креирав сопствена методологија за управување со резервни копии на повеќе MS-SQL сервери, поминав многу време проучувајќи го механизмот за пренесување вредности во Powershell за време на далечински повици, па си пишувам потсетник во случај да е корисно на некој друг.

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

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

За да стартувам скрипти, ќе ја користам следната CMD-датотека, нема да ја вклучувам секој пат:

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

На екранот ќе го видиме следново:

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


Сега да ја извршиме истата скрипта преку WSMAN (далечински):

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

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

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

Одлично, Errorlevel некаде исчезна, но треба да ја добиеме вредноста од сценариото! Ајде да ја пробаме следната конструкција:

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

Ова е уште поинтересно. Пораката во Output исчезна некаде:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Сега, како лирска дигресија, ќе забележам дека ако во функцијата Powershell напишете Write-Output или само израз без да го доделите на која било променлива (и тоа имплицитно имплицира излез на излезниот канал), тогаш дури и кога се извршува локално, ништо нема да се прикаже на екранот! Ова е последица на архитектурата на гасоводот powershell - секоја функција има свој Output pipeline, за неа се создава низа и сè што влегува во неа се смета за резултат на извршување на функцијата, операторот Return ја додава повратната вредност на истата гасоводот како последен елемент и ја пренесува контролата на функцијата за повикување. За илустрација, да ја извршиме следнава скрипта локално:

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

Главната функција (тело на скрипта) исто така има свој Output pipeline, и ако ја извршиме првата скрипта од CMD, пренасочувајќи го излезот во датотека,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

тогаш ќе видиме на екранот

ERRORLEVEL=1

и во досието

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

ако направиме сличен повик од powershell

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

тогаш ќе биде на екранот

Out to host.
ExitCode: 1

и во досието

Out to output.
1

Ова се случува затоа што CMD лансира powershell, кој, во отсуство на други инструкции, меша две нишки (Host и Output) и ги дава на CMD, кој испраќа сè што примил во датотека, а во случај на стартување од powershell, овие две нишки постојат одделно, а пренасочувањата на симболот влијаат само на излезот.

Враќајќи се на главната тема, да потсетиме дека моделот на објектот .NET во powershell целосно постои во еден компјутер (еден оперативен систем), кога кодот се извршува од далечина преку WSMAN, преносот на објектите се случува преку XML серијализација, што носи голем дополнителен интерес на нашето истражување. Ајде да продолжиме со нашите експерименти со извршување на следниов код:

$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

Одличен резултат! Тоа значи дека при повикување Invoke-Command се одржува поделбата на цевководите на две нишки (Host и Output), што ни дава надеж за успех. Ајде да се обидеме да оставиме само една вредност во протокот Output, за што ќе ја промениме првата скрипта што ја извршуваме од далечина:

$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

Ајде да се обидеме да откриеме што се случило. Локално го повикавме powershell, кој пак го повика powershell на оддалечениот компјутер и таму ја изврши нашата скрипта. Два стрима (домаќин и излез) од далечинската машина беа серијализирани и пренесени назад, додека излезниот поток, со една дигитална вредност во него, беше претворен во тип Int32 и како таков беше пренесен на страната примач, а страната примач го користеше како излезна шифра на моќната школка на повикувачот.

И како последна проверка, ајде да создадеме работа во еден чекор на SQL-серверот со типот „Оперативен систем (cmdexec)“ со следниов текст:

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.  Шаг завершился с ошибкой.

Заклучоци:

  • Избегнувајте користење Write-Output и наведување изрази без доделување. Имајте предвид дека преместувањето на овој код на друго место во скриптата може да произведе неочекувани резултати.
  • Во скриптите наменети не за рачно стартување, туку за употреба во вашите механизми за автоматизација, особено за далечински повици преку WINRM, направете рачно справување со грешки преку Try/Catch и погрижете се, при секој развој на настани, оваа скрипта испраќа точно една вредност на примитивен тип . Ако сакате да го добиете класичното ниво на грешка, оваа вредност мора да биде нумеричка.

Извор: www.habr.com

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