Mengembalikan nilai daripada powershell invoke-command kepada ejen SQL Server

Apabila mencipta metodologi saya sendiri untuk menguruskan sandaran pada berbilang pelayan MS-SQL, saya menghabiskan banyak masa untuk mengkaji mekanisme untuk menghantar nilai dalam Powershell semasa panggilan jauh, jadi saya menulis peringatan kepada diri saya sendiri sekiranya ia berguna kepada orang lain.

Jadi, mari kita mulakan dengan skrip mudah dan jalankannya secara tempatan:

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

Untuk menjalankan skrip, saya akan menggunakan fail CMD berikut, saya tidak akan memasukkannya setiap kali:

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

Pada skrin kita akan melihat perkara berikut:

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


Sekarang mari jalankan skrip yang sama melalui WSMAN (dari jauh):

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

Dan inilah hasilnya:

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

Hebat, Errorlevel telah hilang entah ke mana, tetapi kita perlu mendapatkan nilai daripada skrip! Jom cuba reka bentuk berikut:

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

Ini lebih menarik. Mesej dalam Output telah hilang di suatu tempat:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Sekarang, sebagai penyimpangan lirik, saya akan ambil perhatian bahawa jika di dalam fungsi Powershell anda menulis Write-Output atau hanya ungkapan tanpa memberikannya kepada mana-mana pembolehubah (dan ini secara tersirat membayangkan output ke saluran Output), maka walaupun berjalan secara tempatan, tiada apa yang akan dipaparkan pada skrin! Ini adalah akibat daripada seni bina saluran paip powershell - setiap fungsi mempunyai saluran paip Output sendiri, tatasusunan dicipta untuknya, dan semua yang masuk ke dalamnya dianggap hasil pelaksanaan fungsi, operator Return menambah nilai pulangan kepada yang sama saluran paip sebagai elemen terakhir dan memindahkan kawalan ke fungsi panggilan. Untuk menggambarkan, mari jalankan skrip berikut secara setempat:

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

Dan inilah hasilnya:

Main: ParameterValue

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

Fungsi utama (badan skrip) juga mempunyai saluran paip Output sendiri, dan jika kita menjalankan skrip pertama dari CMD, mengalihkan output ke fail,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

maka kita akan lihat pada skrin

ERRORLEVEL=1

dan dalam fail

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

jika kita membuat panggilan serupa daripada powershell

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

maka ia akan berada di skrin

Out to host.
ExitCode: 1

dan dalam fail

Out to output.
1

Ini berlaku kerana CMD melancarkan powershell, yang, jika tiada arahan lain, mencampurkan dua utas (Host dan Output) dan memberikannya kepada CMD, yang menghantar semua yang diterima ke fail, dan dalam kes pelancaran daripada powershell, kedua-dua utas ini wujud secara berasingan, dan ubah hala simbol hanya mempengaruhi Output.

Kembali ke topik utama, mari kita ingat bahawa model objek .NET di dalam powershell wujud sepenuhnya dalam satu komputer (satu OS), apabila menjalankan kod dari jauh melalui WSMAN, pemindahan objek berlaku melalui pensirilan XML, yang membawa banyak minat tambahan kepada penyelidikan kami. Mari teruskan percubaan kami dengan menjalankan kod berikut:

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

Dan inilah yang kami ada pada skrin:

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

Keputusan yang hebat! Ini bermakna apabila memanggil Invoke-Command, pembahagian saluran paip kepada dua utas (Host dan Output) dikekalkan, yang memberi kita harapan untuk berjaya. Mari cuba tinggalkan hanya satu nilai dalam aliran Output, yang mana kami akan menukar skrip pertama yang kami jalankan dari jauh:

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

Mari jalankan seperti ini:

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

dan... YA, ia kelihatan seperti kemenangan!

Out to host.
ExitCode: 4

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


ERRORLEVEL=4

Mari cuba fikirkan apa yang berlaku. Kami memanggil powershell secara tempatan, yang seterusnya memanggil powershell pada komputer jauh dan melaksanakan skrip kami di sana. Dua strim (Hos dan Output) dari mesin jauh telah disiri dan dihantar semula, manakala aliran Output, yang mempunyai satu nilai digital di dalamnya, ditukar kepada menaip Int32 dan oleh itu dihantar ke bahagian penerima, dan bahagian penerima menggunakannya. sebagai kod keluar bagi powershell pemanggil.

Dan sebagai semakan akhir, mari buat kerja satu langkah pada pelayan SQL dengan jenis "Sistem pengendalian (cmdexec)" dengan teks berikut:

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

HORAY! Tugas selesai dengan ralat, teks dalam log:

ВыполняСтся ΠΎΡ‚ ΠΈΠΌΠ΅Π½ΠΈ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ: DOMAINagentuser. Out to host. ExitCode: 6.  Код Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½ΠΈΡ процСсса 6.  Π¨Π°Π³ Π·Π°Π²Π΅Ρ€ΡˆΠΈΠ»ΡΡ с ошибкой.

Kesimpulan:

  • Elakkan menggunakan Tulis-Output dan menyatakan ungkapan tanpa tugasan. Harap maklum bahawa memindahkan kod ini ke tempat lain dalam skrip mungkin menghasilkan hasil yang tidak dijangka.
  • Dalam skrip yang dimaksudkan bukan untuk pelancaran manual, tetapi untuk digunakan dalam mekanisme automasi anda, terutamanya untuk panggilan jauh melalui WINRM, lakukan pengendalian ralat manual melalui Try/Catch dan pastikan, dalam sebarang perkembangan acara, skrip ini menghantar tepat satu nilai jenis primitif . Jika anda ingin mendapatkan Errorlevel klasik, nilai ini mestilah angka.

Sumber: www.habr.com

Tambah komen