Khi tạo phương pháp của riêng mình để quản lý các bản sao lưu trên nhiều máy chủ MS-SQL, tôi đã dành rất nhiều thời gian nghiên cứu cơ chế truyền các giá trị trong Powershell trong các cuộc gọi từ xa, vì vậy tôi đang viết lời nhắc cho chính mình trong trường hợp nó hữu ích đên ngươi nao khac.
Vì vậy, hãy bắt đầu với một tập lệnh đơn giản và chạy nó cục bộ:
$exitcode = $args[0]
Write-Host 'Out to host.'
Write-Output 'Out to output.'
Write-Host ('ExitCode: ' + $exitcode)
Write-Output $exitcode
$host.SetShouldExit($exitcode)Để chạy tập lệnh, tôi sẽ sử dụng tệp CMD sau, tôi sẽ không đưa nó vào mọi lúc:
@Echo OFF
PowerShell .TestOutput1.ps1 1
ECHO ERRORLEVEL=%ERRORLEVEL%Trên màn hình chúng ta sẽ thấy như sau:
Out to host.
Out to output.
ExitCode: 1
1
ERRORLEVEL=1
Bây giờ hãy chạy cùng một tập lệnh thông qua WSMAN (từ xa):
Invoke-Command -ComputerName . -ScriptBlock { &'D:sqlagentTestOutput1.ps1' $args[0] } -ArgumentList $args[0]Và đây là kết quả dành cho bạn:
Out to host.
Out to output.
ExitCode: 2
2
ERRORLEVEL=0Tuyệt vời, Errorlevel đã biến mất ở đâu đó, nhưng chúng ta cần lấy giá trị từ tập lệnh! Hãy thử thiết kế sau:
$res=Invoke-Command -ComputerName . -ScriptBlock { &'D:sqlagentTestOutput1.ps1' $args[0] } -ArgumentList $args[0]Điều này thậm chí còn thú vị hơn. Thông báo trong Đầu ra đã biến mất ở đâu đó:
Out to host.
ExitCode: 2
ERRORLEVEL=0Bây giờ, dưới dạng lạc đề trữ tình, tôi sẽ lưu ý rằng nếu bên trong hàm Powershell bạn viết Đầu ra ghi hoặc chỉ một biểu thức mà không gán nó cho bất kỳ biến nào (và điều này ngầm ám chỉ đầu ra cho kênh Đầu ra), thì ngay cả khi chạy cục bộ, sẽ không có gì được hiển thị trên màn hình! Đây là hệ quả của kiến trúc đường dẫn powershell - mỗi hàm có đường dẫn đầu ra riêng, một mảng được tạo cho nó và mọi thứ đi vào đó đều được coi là kết quả của việc thực thi hàm, toán tử Return thêm giá trị trả về vào cùng đường ống làm phần tử cuối cùng và chuyển quyền điều khiển sang chức năng gọi. Để minh họa, hãy chạy đoạn script sau trên máy cục bộ:
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: "+$_) }
Và đây là kết quả:
Main: ParameterValue
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
2
Main: Function: ParameterValue
Main: ReturnValueHàm chính (nội dung tập lệnh) cũng có đường dẫn đầu ra riêng và nếu chúng ta chạy tập lệnh đầu tiên từ CMD, sẽ chuyển hướng đầu ra sang một tệp,
PowerShell .TestOutput1.ps1 1 > TestOutput1.txt
sau đó chúng ta sẽ thấy trên màn hình
ERRORLEVEL=1và trong tập tin
Out to host.
Out to output.
ExitCode: 1
1
nếu chúng tôi thực hiện một cuộc gọi tương tự từ powershell
PS D:sqlagent> .TestOutput1.ps1 1 > TestOutput1.txtsau đó nó sẽ xuất hiện trên màn hình
Out to host.
ExitCode: 1và trong tập tin
Out to output.
1Điều này xảy ra do CMD khởi chạy powershell, trong trường hợp không có hướng dẫn khác, sẽ trộn hai luồng (Máy chủ và Đầu ra) và đưa chúng cho CMD, CMD sẽ gửi mọi thứ nó nhận được vào một tệp và trong trường hợp khởi chạy từ powershell, hai luồng này tồn tại riêng biệt và chuyển hướng biểu tượng chỉ ảnh hưởng đến Đầu ra.
Quay lại chủ đề chính, chúng ta hãy nhớ rằng mô hình đối tượng .NET bên trong powershell tồn tại hoàn toàn trong một máy tính (một hệ điều hành), khi chạy mã từ xa qua WSMAN, việc truyền các đối tượng xảy ra thông qua tuần tự hóa XML, điều này mang lại rất nhiều điều thú vị. đến nghiên cứu của chúng tôi. Hãy tiếp tục thử nghiệm của chúng tôi bằng cách chạy đoạn mã sau:
$res=Invoke-Command -ComputerName . -ScriptBlock { &'D:sqlagentTestOutput1.ps1' $args[0] } -ArgumentList $args[0]
$res.GetType()
$host.SetShouldExit($res)
Và đây là những gì chúng ta có trên màn hình:
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=0Kết quả tuyệt vời! Điều đó có nghĩa là khi gọi Invoke-Command, việc phân chia các đường ống thành hai luồng (Máy chủ và Đầu ra) được duy trì, điều này mang lại cho chúng ta hy vọng thành công. Hãy cố gắng chỉ để lại một giá trị trong luồng Đầu ra, trong đó chúng tôi sẽ thay đổi tập lệnh đầu tiên mà chúng tôi chạy từ xa:
$exitcode = $args[0]
Write-Host 'Out to host.'
#Write-Output 'Out to output.'
Write-Host ('ExitCode: ' + $exitcode)
Write-Output $exitcode
$host.SetShouldExit($exitcode)
Hãy chạy nó như thế này:
$res=Invoke-Command -ComputerName . -ScriptBlock { &'D:sqlagentTestOutput1.ps1' $args[0] } -ArgumentList $args[0]
$host.SetShouldExit($res)
và... CÓ, có vẻ như là một chiến thắng!
Out to host.
ExitCode: 4
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
ERRORLEVEL=4Chúng ta hãy cố gắng tìm hiểu những gì đã xảy ra. Chúng tôi gọi powershell cục bộ, từ đó gọi powershell trên máy tính từ xa và thực thi tập lệnh của chúng tôi ở đó. Hai luồng (Máy chủ và Đầu ra) từ máy từ xa đã được tuần tự hóa và gửi lại, trong khi luồng Đầu ra, có một giá trị kỹ thuật số duy nhất trong đó, được chuyển đổi thành loại Int32 và như vậy được chuyển sang bên nhận và bên nhận đã sử dụng nó làm mã thoát của powershell người gọi.
Và để kiểm tra lần cuối, chúng ta hãy tạo ra người phục vụ Một tác vụ SQL một bước thuộc loại "Hệ điều hành (cmdexec)" với nội dung như sau:
PowerShell -NonInteractive -NoProfile "$res=Invoke-Command -ComputerName BACKUPSERVER -ConfigurationName SQLAgent -ScriptBlock {&'D:sqlagentTestOutput1.ps1' 6}; $host.SetShouldExit($res)"HOAN HÔ! Tác vụ đã hoàn thành có lỗi, nội dung trong nhật ký:
Выполняется от имени пользователя: DOMAINagentuser. Out to host. ExitCode: 6. Код завершения процесса 6. Шаг завершился с ошибкой.
Kết luận:
- Tránh sử dụng Write-Output và chỉ định biểu thức mà không gán. Xin lưu ý rằng việc di chuyển mã này đến nơi khác trong tập lệnh có thể tạo ra kết quả không mong muốn.
- Trong các tập lệnh không nhằm mục đích khởi chạy thủ công mà để sử dụng trong các cơ chế tự động hóa của bạn, đặc biệt là cho các cuộc gọi từ xa qua WINRM, hãy xử lý lỗi thủ công thông qua Try/Catch và đảm bảo rằng, trong bất kỳ quá trình phát triển sự kiện nào, tập lệnh này sẽ gửi chính xác một giá trị loại nguyên thủy . Nếu bạn muốn lấy Errorlevel cổ điển, giá trị này phải là số.
Nguồn: www.habr.com
