Kthimi i një vlere nga komanda e thirrjes së powershell në agjentin SQL Server

Kur krijova metodologjinë time për menaxhimin e kopjeve rezervë në serverë të shumtë MS-SQL, kalova shumë kohë duke studiuar mekanizmin për kalimin e vlerave në Powershell gjatë thirrjeve në distancë, kështu që po shkruaj një kujtesë për veten time në rast se është e dobishme ndaj dikujt tjetër.

Pra, le të fillojmë me një skenar të thjeshtë dhe ta ekzekutojmë atë në nivel lokal:

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

Për të ekzekutuar skriptet, do të përdor skedarin e mëposhtëm CMD, nuk do ta përfshij çdo herë:

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

Në ekran do të shohim sa vijon:

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


Tani le të ekzekutojmë të njëjtin skenar përmes WSMAN (në distancë):

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

Dhe këtu është rezultati:

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

E shkëlqyeshme, niveli i gabimit është zhdukur diku, por ne duhet të marrim vlerën nga skenari! Le të provojmë dizajnin e mëposhtëm:

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

Kjo është edhe më interesante. Mesazhi në Output është zhdukur diku:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Tani, si një digresion lirik, do të vërej se nëse brenda një funksioni Powershell shkruani Write-Output ose thjesht një shprehje pa e caktuar atë në ndonjë variabël (dhe kjo nënkupton në mënyrë implicite dalje në kanalin Output), atëherë edhe kur ekzekutohet në nivel lokal, asgjë nuk do të shfaqet në ekran! Kjo është pasojë e arkitekturës së tubacionit powershell - secili funksion ka tubacionin e tij të daljes, krijohet një grup për të dhe gjithçka që hyn në të konsiderohet rezultat i ekzekutimit të funksionit, operatori Return shton vlerën e kthimit në të njëjtën tubacioni si elementi i fundit dhe transferon kontrollin në funksionin thirrës. Për ta ilustruar, le të ekzekutojmë skriptin e mëposhtëm në nivel lokal:

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

Dhe këtu është rezultati:

Main: ParameterValue

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

Funksioni kryesor (trupi i skriptit) ka gjithashtu linjën e tij të daljes, dhe nëse ekzekutojmë skriptin e parë nga CMD, duke e ridrejtuar daljen në një skedar,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

atëherë do të shohim në ekran

ERRORLEVEL=1

dhe në dosje

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

nëse bëjmë një thirrje të ngjashme nga powershell

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

atëherë do të jetë në ekran

Out to host.
ExitCode: 1

dhe në dosje

Out to output.
1

Kjo ndodh sepse CMD lëshon powershell, i cili, në mungesë të udhëzimeve të tjera, përzien dy threads (Host dhe Output) dhe ia jep CMD-së, e cila dërgon gjithçka që ka marrë në një skedar, dhe në rastin e nisjes nga powershell, këto dy fije ekzistojnë veçmas, dhe ridrejtimet e simboleve ndikojnë vetëm në Output.

Duke iu rikthyer temës kryesore, le të kujtojmë se modeli i objektit .NET brenda powershell ekziston plotësisht brenda një kompjuteri (një OS), kur kodi ekzekutohet nga distanca përmes WSMAN, transferimi i objekteve ndodh përmes serializimit XML, i cili sjell shumë interes shtesë. për kërkimin tonë. Le të vazhdojmë eksperimentet tona duke ekzekutuar kodin e mëposhtëm:

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

Dhe kjo është ajo që kemi në ekran:

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

Rezultat i madh! Do të thotë që kur thirret Invoke-Command, ruhet ndarja e tubacioneve në dy threads (Host dhe Output), gjë që na jep shpresë për sukses. Le të përpiqemi të lëmë vetëm një vlerë në rrjedhën Output, për të cilën do të ndryshojmë skriptin e parë që ekzekutojmë nga distanca:

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

Le ta drejtojmë kështu:

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

dhe... PO, duket si fitore!

Out to host.
ExitCode: 4

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


ERRORLEVEL=4

Le të përpiqemi të kuptojmë se çfarë ka ndodhur. Ne thirrëm powershell në nivel lokal, i cili nga ana e tij thirri powershell në kompjuterin e largët dhe ekzekutoi skriptin tonë atje. Dy rryma (Host dhe Output) nga makina në distancë u serializuan dhe u kaluan prapa, ndërsa rryma Output, me një vlerë të vetme dixhitale në të, u konvertua në llojin Int32 dhe si i tillë kaloi në anën marrëse, dhe pala marrëse e përdori atë. si kodi i daljes së Powershell-it të thirrësit.

Dhe si një kontroll përfundimtar, le të krijojmë një punë me një hap në serverin SQL me llojin "Sistemi operativ (cmdexec)" me tekstin e mëposhtëm:

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

URA! Detyra e përfunduar me një gabim, teksti në regjistër:

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

Konkluzione:

  • Shmangni përdorimin e Write-Output dhe specifikimin e shprehjeve pa caktim. Kini parasysh se zhvendosja e këtij kodi diku tjetër në skenar mund të prodhojë rezultate të papritura.
  • Në skriptet e destinuara jo për nisje manuale, por për përdorim në mekanizmat tuaj të automatizimit, veçanërisht për thirrjet në distancë nëpërmjet WINRM, bëni trajtimin manual të gabimeve përmes Try/Catch dhe sigurohuni që, në çdo zhvillim ngjarjesh, ky skript të dërgojë saktësisht një vlerë të tipit primitiv . Nëse dëshironi të merrni nivelin klasik të gabimit, kjo vlerë duhet të jetë numerike.

Burimi: www.habr.com

Shto një koment