เมื่อสร้างวิธีการของตัวเองในการจัดการการสำรองข้อมูลบนเซิร์ฟเวอร์ 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