Resendi valoron de powershell invoke-command al SQL-Servilo-agento

Kreante mian propran metodaron por administri sekurkopiojn sur pluraj MS-SQL-serviloj, mi pasigis multan tempon por studi la mekanismon por transdoni valorojn en Powershell dum foraj vokoj, do mi skribas memorigilon al mi se ĝi estos utila. al iu alia.

Do, ni komencu per simpla skripto kaj rulu ĝin loke:

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

Por ruli skriptojn, mi uzos la sekvan CMD-dosieron, mi ne inkludos ĝin ĉiufoje:

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

Sur la ekrano ni vidos la jenon:

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


Nun ni rulu la saman skripton per WSMAN (fore):

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

Kaj jen la rezulto:

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

Bonege, Errorlevel malaperis ie, sed ni devas akiri la valoron de la skripto! Ni provu la sekvan dezajnon:

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

Ĉi tio estas eĉ pli interesa. La mesaĝo en Eligo malaperis ie:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Nun, kiel lirika digresio, mi rimarkos, ke se ene de Powershell-funkcio vi skribas Write-Output aŭ nur esprimon sen atribui ĝin al iu variablo (kaj ĉi tio implicite implicas eligon al la Eligo-kanalo), tiam eĉ kiam funkcias loke, nenio estos montrata sur la ekrano! Ĉi tio estas sekvo de la powershell-dukto-arkitekturo - ĉiu funkcio havas sian propran Elig-dukton, tabelo estas kreita por ĝi, kaj ĉio, kio eniras ĝin, estas konsiderata la rezulto de la funkcia ekzekuto, la Revena operatoro aldonas la revenan valoron al la sama. dukto kiel la lasta elemento kaj transdonas kontrolon al la vokanta funkcio. Por ilustri, ni rulu la sekvan skripton loke:

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: "+$_) }

Kaj jen la rezulto:

Main: ParameterValue

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

La ĉefa funkcio (skriptkorpo) ankaŭ havas sian propran Eligo-dukton, kaj se ni rulas la unuan skripton de CMD, redirektante la eligon al dosiero,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

tiam ni vidos sur la ekrano

ERRORLEVEL=1

kaj en la dosiero

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

se ni faras similan vokon de powershell

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

tiam ĝi estos sur la ekrano

Out to host.
ExitCode: 1

kaj en la dosiero

Out to output.
1

Ĉi tio okazas ĉar la CMD lanĉas powershell, kiu, en foresto de aliaj instrukcioj, miksas du fadenojn (Gastiganto kaj Eligo) kaj donas ilin al la CMD, kiu sendas ĉion, kion ĝi ricevis al dosiero, kaj en la kazo de kurado de powershell, ĉi tiuj du fadenoj ekzistas aparte, kaj la simbolaj alidirektiloj nur influas Eligon.

Revenante al la ĉefa temo, ni memoru, ke la .NET-objektmodelo ene de powershell plene ekzistas ene de unu komputilo (unu OS), kiam oni rulas kodon malproksime per WSMAN, la translokigo de objektoj okazas per XML-serialigo, kiu alportas multe da plia intereso. al nia esploro. Ni daŭrigu niajn eksperimentojn rulante la sekvan kodon:

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

Kaj jen kion ni havas sur la ekrano:

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

Bonega rezulto! Ĝi signifas, ke kiam oni vokas Invoke-Command, la divido de la duktoj en du fadenojn (Gastiganto kaj Eligo) estas konservita, kio donas al ni esperon pri sukceso. Ni provu lasi nur unu valoron en la Eligo-fluo, por kiu ni ŝanĝos la plej unuan skripton, kiun ni rulas malproksime:

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

Ni rulu ĝin tiel:

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

kaj... JES, ĝi aspektas kiel venko!

Out to host.
ExitCode: 4

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


ERRORLEVEL=4

Ni provu eltrovi kio okazis. Ni vokis powershell loke, kiu siavice nomis powershell sur la fora komputilo kaj ekzekutis nian skripton tie. Du fluoj (Gastiganto kaj Eligo) de la fora maŝino estis seriigitaj kaj pasitaj reen, dum la Eligo-rivereto, havanta ununuran ciferecan valoron en ĝi, estis konvertita al tipo Int32 kaj kiel tia pasita al la ricevanta flanko, kaj la ricevanta flanko uzis ĝin. kiel la elirkodo de la alvokanto powershell.

Kaj kiel fina kontrolo, ni kreu unupaŝan laboron en la SQL-servilo kun la tipo "Mastruma sistemo (cmdexec)" kun la sekva teksto:

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

HURA! La tasko kompletigita kun eraro, teksto en la protokolo:

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

Konkludoj:

  • Evitu uzi Write-Output kaj specifi esprimojn sen tasko. Konsciu, ke movi ĉi tiun kodon aliloken en la skripto povas produkti neatenditajn rezultojn.
  • En skriptoj destinitaj ne por mana lanĉo, sed por uzo en viaj aŭtomatigaj mekanismoj, precipe por foraj vokoj per WINRM, faru manan erartraktadon per Try/Catch, kaj certigu, ke, en ajna evoluo de eventoj, ĉi tiu skripto sendas ĝuste unu primitivan tipvaloron. . Se vi volas akiri la klasikan Erarnivelon, ĉi tiu valoro devas esti nombra.

fonto: www.habr.com

Aldoni komenton