Mengembalikan nilai dari perintah pemanggilan PowerShell ke agen SQL Server

Saat membuat metodologi saya sendiri untuk mengelola cadangan di beberapa server MS-SQL, saya menghabiskan banyak waktu mempelajari mekanisme untuk meneruskan nilai di Powershell selama panggilan jarak jauh, jadi saya menulis pengingat untuk diri saya sendiri jika itu berguna untuk orang lain.

Jadi, mari kita mulai dengan skrip sederhana dan menjalankannya secara lokal:

$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 file CMD berikut, saya tidak akan menyertakannya setiap saat:

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

Di layar kita akan melihat yang berikut:

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


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

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

Dan inilah hasilnya untuk Anda:

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

Hebat, Errorlevel telah hilang entah kemana, tapi kita perlu mendapatkan nilainya dari skrip! Mari kita coba desain berikut:

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

Ini bahkan lebih menarik lagi. Pesan di Output telah hilang entah kemana:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Sekarang, sebagai penyimpangan liris, saya akan mencatat bahwa jika di dalam fungsi Powershell Anda menulis Write-Output atau hanya sebuah ekspresi tanpa menetapkannya ke variabel apa pun (dan ini secara implisit menyiratkan keluaran ke saluran Output), bahkan ketika dijalankan secara lokal, tidak ada yang akan ditampilkan di layar! Ini adalah konsekuensi dari arsitektur pipa PowerShell - setiap fungsi memiliki pipa Outputnya sendiri, sebuah array dibuat untuknya, dan segala sesuatu yang masuk ke dalamnya dianggap sebagai hasil eksekusi fungsi, operator Return menambahkan nilai kembalian ke fungsi yang sama. pipeline sebagai elemen terakhir dan mentransfer kontrol ke fungsi pemanggil. Sebagai ilustrasi, mari jalankan skrip berikut secara 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: "+$_) }

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 memiliki pipa Outputnya sendiri, dan jika kita menjalankan skrip pertama dari CMD, mengarahkan output ke file,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

maka kita akan melihat di layar

ERRORLEVEL=1

dan di dalam file

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

jika kita melakukan panggilan serupa dari PowerShell

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

maka itu akan muncul di layar

Out to host.
ExitCode: 1

dan di dalam file

Out to output.
1

Hal ini terjadi karena CMD meluncurkan PowerShell, yang, jika tidak ada instruksi lain, menggabungkan dua thread (Host dan Output) dan memberikannya ke CMD, yang mengirimkan semua yang diterimanya ke sebuah file, dan jika diluncurkan dari PowerShell, kedua utas ini ada secara terpisah, dan pengalihan simbol hanya memengaruhi Output.

Kembali ke topik utama, mari kita ingat bahwa model objek .NET di dalam PowerShell sepenuhnya ada dalam satu komputer (satu OS), ketika menjalankan kode dari jarak jauh melalui WSMAN, transfer objek terjadi melalui serialisasi XML, yang membawa banyak tambahan minat untuk penelitian kami. Mari lanjutkan eksperimen kita dengan menjalankan kode berikut:

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

Dan inilah yang kita lihat di layar:

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

Hasil yang bagus! Ini berarti bahwa ketika Invoke-Command dipanggil, pembagian pipeline menjadi dua thread (Host dan Output) dipertahankan, yang memberi kita harapan untuk sukses. Mari kita coba menyisakan hanya satu nilai di aliran Output, yang mana kita akan mengubah skrip pertama yang kita jalankan dari jarak 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 kita jalankan seperti ini:

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

dan... YA, sepertinya sebuah kemenangan!

Out to host.
ExitCode: 4

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


ERRORLEVEL=4

Mari kita coba mencari tahu apa yang terjadi. Kami memanggil PowerShell secara lokal, yang selanjutnya memanggil PowerShell di komputer jarak jauh dan mengeksekusi skrip kami di sana. Dua aliran (Host dan Output) dari mesin jarak jauh diserialkan dan diteruskan kembali, sedangkan aliran Output, yang memiliki nilai digital tunggal di dalamnya, diubah menjadi tipe Int32 dan dengan demikian diteruskan ke sisi penerima, dan sisi penerima menggunakannya sebagai kode keluar dari PowerShell pemanggil.

Dan sebagai pemeriksaan terakhir, mari kita buat pekerjaan satu langkah di server SQL dengan tipe “Sistem operasi (cmdexec)” dengan teks berikut:

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

hore! Tugas selesai dengan kesalahan, teks di log:

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

Kesimpulan:

  • Hindari menggunakan Write-Output dan menentukan ekspresi tanpa penetapan. Sadarilah bahwa memindahkan kode ini ke tempat lain dalam skrip dapat memberikan hasil yang tidak diharapkan.
  • Dalam skrip yang dimaksudkan bukan untuk peluncuran manual, tetapi untuk digunakan dalam mekanisme otomasi Anda, terutama untuk panggilan jarak jauh melalui WINRM, lakukan penanganan kesalahan manual melalui Coba/Tangkap, dan pastikan bahwa, dalam perkembangan peristiwa apa pun, skrip ini mengirimkan tepat satu nilai tipe primitif . Jika Anda ingin mendapatkan Errorlevel klasik, nilai ini harus berupa numerik.

Sumber: www.habr.com

Tambah komentar