ส่งคืนค่าจากคำสั่งเรียกใช้ 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 - แต่ละฟังก์ชันมีไปป์ไลน์เอาต์พุตของตัวเองอาร์เรย์ถูกสร้างขึ้นสำหรับมันและทุกสิ่งที่เข้าไปจะถือเป็นผลลัพธ์ของการทำงานของฟังก์ชันตัวดำเนินการ 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

ฟังก์ชั่นหลัก (เนื้อหาสคริปต์) ยังมีไปป์ไลน์เอาท์พุตของตัวเอง และหากเรารันสคริปต์แรกจาก 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 ซึ่งหากไม่มีคำแนะนำอื่น ๆ จะผสมสองเธรด (โฮสต์และเอาต์พุต) และมอบให้กับ 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 การแบ่งไปป์ไลน์ออกเป็นสองเธรด (โฮสต์และเอาท์พุต) ยังคงอยู่ซึ่งทำให้เรามีความหวังสำหรับความสำเร็จ ลองปล่อยค่าเดียวไว้ในเอาท์พุตสตรีม ซึ่งเราจะเปลี่ยนสคริปต์แรกสุดที่เราเรียกใช้จากระยะไกล:

$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 และด้วยเหตุนี้จึงส่งผ่านไปยังฝั่งรับ และฝั่งรับก็ใช้มัน เป็นรหัสทางออกของ PowerShell ผู้โทร

และเพื่อเป็นการตรวจสอบขั้นสุดท้าย เรามาสร้างงานขั้นตอนเดียวบนเซิร์ฟเวอร์ 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 และตรวจสอบให้แน่ใจว่าในการพัฒนาเหตุการณ์ใดๆ สคริปต์นี้จะส่งค่าประเภทดั้งเดิมเพียงค่าเดียวเท่านั้น . หากคุณต้องการรับ Errorlevel แบบคลาสสิก ค่านี้ต้องเป็นตัวเลข

ที่มา: will.com

เพิ่มความคิดเห็น