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-ն գործարկում է powershell-ը, որը, այլ հրահանգների բացակայության դեպքում, խառնում է երկու շարանը (Host և Output) և դրանք տալիս է CMD-ին, որն ուղարկում է այն ամենը, ինչ ստացել է ֆայլ, իսկ powershell-ից գործարկելու դեպքում, այս երկու շարանը գոյություն ունի առանձին, և խորհրդանիշի վերահղումները միայն ազդում են Արդյունքի վրա:

Վերադառնալով բուն թեմային, հիշենք, որ Powershell-ի ներսում .NET օբյեկտի մոդելը լիովին գոյություն ունի մեկ համակարգչի (մեկ ՕՀ) ներսում, 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 և այնտեղ գործարկեց մեր սցենարը: Հեռավոր մեքենայից երկու հոսքեր (Հոսթ և Ելք) սերիականացվել և հետ են փոխանցվել, մինչդեռ Ելքային հոսքը, ունենալով մեկ թվային արժեք, վերածվել է 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, ապա այս արժեքը պետք է լինի թվային:

Source: www.habr.com

Добавить комментарий