برگرداندن مقداری از powershell invoke-command به عامل 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]

این جالب تر است. پیام در خروجی در جایی ناپدید شده است:

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 پاورشل را راه اندازی می کند، که در غیاب دستورالعمل های دیگر، دو رشته (Host و Output) را با هم ترکیب می کند و آنها را به CMD می دهد، که هر چیزی را که دریافت کرده است به یک فایل ارسال می کند، و در صورت اجرا از 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، تقسیم خطوط لوله به دو رشته (Host و Output) حفظ می شود که ما را به موفقیت امیدوار می کند. بیایید سعی کنیم فقط یک مقدار را در جریان خروجی بگذاریم، که اولین اسکریپت را که از راه دور اجرا می کنیم تغییر می دهیم:

$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 را در رایانه راه دور صدا زد و اسکریپت ما را در آنجا اجرا کرد. دو استریم (Host و Output) از دستگاه راه دور سریالی شده و به عقب منتقل شدند، در حالی که جریان خروجی با داشتن یک مقدار دیجیتال واحد به نوع Int32 تبدیل شد و به این ترتیب به سمت گیرنده منتقل شد و طرف گیرنده از آن استفاده کرد. به عنوان کد خروجی پاورشل تماس گیرنده.

و به عنوان آخرین بررسی، بیایید یک کار یک مرحله ای در سرور 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 کلاسیک را دریافت کنید، این مقدار باید عددی باشد.

منبع: www.habr.com

اضافه کردن نظر