Επιστροφή μιας τιμής από την εντολή-επίκληση του powershell στον παράγοντα 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]

Αυτό είναι ακόμα πιο ενδιαφέρον. Το μήνυμα στο Output έχει εξαφανιστεί κάπου:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Τώρα, ως λυρική παρέκβαση, θα σημειώσω ότι εάν μέσα σε μια συνάρτηση Powershell γράψετε Write-Output ή απλώς μια έκφραση χωρίς να την αντιστοιχίσετε σε καμία μεταβλητή (και αυτό υπονοεί σιωπηρά έξοδο στο κανάλι εξόδου), τότε ακόμα και όταν εκτελείται τοπικά, τίποτα δεν θα εμφανίζεται στην οθόνη! Αυτό είναι συνέπεια της αρχιτεκτονικής αγωγών powershell - κάθε συνάρτηση έχει τη δική της γραμμή εξόδου, δημιουργείται ένας πίνακας για αυτήν και ό,τι μπαίνει σε αυτόν θεωρείται το αποτέλεσμα της εκτέλεσης της συνάρτησης, ο τελεστής Return προσθέτει την τιμή επιστροφής στην ίδια pipeline ως το τελευταίο στοιχείο και μεταφέρει τον έλεγχο στη λειτουργία κλήσης. Για να το επεξηγήσουμε, ας εκτελέσουμε το ακόλουθο σενάριο τοπικά:

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, Αυτά τα δύο νήματα υπάρχουν χωριστά και οι ανακατευθύνσεις συμβόλων επηρεάζουν μόνο την έξοδο.

Επιστρέφοντας στο κύριο θέμα, ας θυμηθούμε ότι το μοντέλο αντικειμένου .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 διατηρείται η διαίρεση των αγωγών σε δύο νήματα (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 και ως εκ τούτου πέρασε στην πλευρά λήψης και η πλευρά λήψης τη χρησιμοποιούσε ως κωδικός εξόδου του καλούντος 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 και βεβαιωθείτε ότι, σε οποιαδήποτε εξέλιξη συμβάντων, αυτό το σενάριο στέλνει ακριβώς μια τιμή πρωτόγονου τύπου . Εάν θέλετε να λάβετε το κλασικό επίπεδο σφάλματος, αυτή η τιμή πρέπει να είναι αριθμητική.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο