إرجاع القيمة من أمر استدعاء powershell إلى عامل خادم SQL

عند إنشاء منهجية إدارة النسخ الاحتياطي الخاصة بي على العديد من خوادم MS-SQL ، قضيت الكثير من الوقت في دراسة آلية نقل القيم إلى powerhell للمكالمات عن بعد ، لذلك أنا أكتب مذكرة لنفسي ، وفجأة ستصبح في متناول اليد لشخص آخر.

لذا ، لنأخذ نصًا بسيطًا للبدء به وتشغيله محليًا:

$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:\sqlagent\TestOutput1.ps1' $args[0] } -ArgumentList $args[0]

وها هي النتيجة:

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

بأعجوبة ، اختفت Errorlevel في مكان ما ، لكننا بحاجة للحصول على القيمة من البرنامج النصي! نحاول البناء التالي:

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

إنه أكثر إثارة للاهتمام. اختفى إخراج الرسالة في الإخراج في مكان ما:

Out to host.
ExitCode: 2
ERRORLEVEL=0

الآن ، كملاحظة غنائية ، ألاحظ أنه إذا كتبت كتابة إخراج أو مجرد تعبير دون تعيينه إلى أي متغير داخل وظيفة Powershell (وهذا يعني ضمنيًا الإخراج إلى قناة الإخراج) ، حتى إذا قمت بتشغيله محليًا ، فلن يتم عرض أي شيء! هذا نتيجة لهيكل خط أنابيب powerhell - كل وظيفة لها خط أنابيب إخراج خاص بها ، ويتم إنشاء مصفوفة لها ، وكل شيء يدخل إليها يعتبر نتيجة للدالة ، تضيف عبارة العودة قيمة الإرجاع إلى نفس خط الأنابيب كعنصر آخر وتحكم في التحكم في وظيفة الاستدعاء. للتوضيح ، قم بتنفيذ البرنامج النصي التالي محليًا:

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

إذا قمنا بإجراء مكالمة مماثلة من بوويرشيل

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


ثم ستكون الشاشة

Out to host.
ExitCode: 1

وفي الملف

Out to output.
1

هذا لأن CMD يبدأ powerhell ، والذي ، في حالة عدم وجود تعليمات أخرى ، يمزج التدفقين (المضيف والإخراج) ويعطيهما إلى CMD ، الذي يرسل كل شيء تم تلقيه إلى الملف ، وإذا تم إطلاقه من powerhell ، فإن هذين التدفقين موجودان بشكل منفصل ، و تؤثر عمليات إعادة التوجيه على الإخراج فقط.

بالعودة إلى الموضوع الرئيسي ، نتذكر أن نموذج كائن .NET داخل powershell موجود بالكامل في إطار كمبيوتر واحد (نظام تشغيل واحد) ، عندما يتم تشغيل التعليمات البرمجية عن بُعد عبر WSMAN ، يتم نقل الكائنات عبر تسلسل XML ، مما يجلب الكثير من الاهتمام الإضافي لأبحاثنا. دعنا نواصل التجارب عن طريق تشغيل الكود التالي:

$res=Invoke-Command -ComputerName . -ScriptBlock { &'D:\sqlagent\TestOutput1.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:\sqlagent\TestOutput3.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:\sqlagent\TestOutput1.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

دعونا نحاول معرفة ما حدث معنا. اتصلنا محليًا بـ powerhell ، والذي أطلق عليه بدوره powerhell على الكمبيوتر البعيد ونفذنا نصنا هناك. تم تسلسل دفقين (المضيف والإخراج) من الآلة البعيدة ونقلهما مرة أخرى ، وتم تحويل دفق الإخراج ، إذا كان هناك قيمة رقمية واحدة فيه ، إلى نوع Int32 ونقله إلى جانب الاستلام في هذا النموذج ، واستخدمه جانب الاستلام كرمز إنهاء الاتصال بوويرشيل.

وكاختبار أخير ، سننشئ على خادم SQL مهمة من خطوة واحدة من النوع "نظام التشغيل (cmdexec)" بالنص التالي:

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

مرحى! فشلت المهمة ، النص في السجل:

   : DOMAIN\agentuser. Out to host. ExitCode: 6.     6.     .

الموجودات:

  • تجنب استخدام إخراج الكتابة وتحديد التعبيرات بدون تعيين. تذكر أن نقل هذا الرمز إلى مكان آخر في البرنامج النصي قد يؤدي إلى نتائج غير متوقعة.
  • في البرامج النصية المخصصة ليس للتشغيل اليدوي ، ولكن للاستخدام في آليات الأتمتة الخاصة بك ، خاصة بالنسبة للمكالمات عن بعد عبر WINRM ، قم بمعالجة الأخطاء يدويًا من خلال Try / Catch ، وتأكد من أن هذا البرنامج النصي يرسل واحدًا بالضبط إلى دفق الإخراج لأي تطور للأحداث قيمة النوع البدائي. إذا كنت ترغب في الحصول على مستوى الخطأ الكلاسيكي - يجب أن تكون هذه القيمة رقمية.

All Articles