Powershell çağırma komutundan SQL Server aracısına bir değer döndürme

Birden fazla MS-SQL sunucusundaki yedeklemeleri yönetmek için kendi metodolojimi oluştururken, uzak aramalar sırasında Powershell'deki değerleri aktarma mekanizmasını incelemek için çok zaman harcadım, bu yüzden yararlı olması durumunda kendime bir hatırlatma yazıyorum başka birine.

Öyleyse basit bir komut dosyasıyla başlayalım ve onu yerel olarak çalıştıralım:

$exitcode = $args[0]
Write-Host 'Out to host.'
Write-Output 'Out to output.'
Write-Host ('ExitCode: ' + $exitcode)
Write-Output $exitcode
$host.SetShouldExit($exitcode)

Betikleri çalıştırmak için aşağıdaki CMD dosyasını kullanacağım, her seferinde eklemeyeceğim:

@Echo OFF
PowerShell .TestOutput1.ps1 1
ECHO ERRORLEVEL=%ERRORLEVEL%

Ekranda aşağıdakileri göreceğiz:

Out to host.
Out to output.
ExitCode: 1
1
ERRORLEVEL=1


Şimdi aynı betiği WSMAN aracılığıyla (uzaktan) çalıştıralım:

Invoke-Command -ComputerName . -ScriptBlock { &'D:sqlagentTestOutput1.ps1' $args[0] } -ArgumentList $args[0]

Ve işte sonuç:

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

Harika, Errorlevel bir yerde kayboldu, ancak değeri komut dosyasından almamız gerekiyor! Aşağıdaki tasarımı deneyelim:

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

Bu daha da ilginç. Çıktıdaki mesaj bir yerde kayboldu:

Out to host.
ExitCode: 2
ERRORLEVEL=0

Şimdi, lirik bir açıklama olarak şunu not edeceğim: Eğer bir Powershell fonksiyonunun içinde, herhangi bir değişkene atamadan Write-Output veya sadece bir ifade yazarsanız (ve bu dolaylı olarak Çıkış kanalına çıktı anlamına gelir), o zaman yerel olarak çalışırken bile, ekranda hiçbir şey görüntülenmeyecek! Bu, powershell işlem hattı mimarisinin bir sonucudur - her işlevin kendi Çıkış işlem hattı vardır, bunun için bir dizi oluşturulur ve içine giren her şey, işlev yürütmenin sonucu olarak kabul edilir, Return operatörü dönüş değerini aynı değere ekler boru hattını son öğe olarak kullanır ve kontrolü çağıran işleve aktarır. Örnek olarak aşağıdaki betiği yerel olarak çalıştıralım:

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: "+$_) }

Ve işte sonuç:

Main: ParameterValue

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array
2
Main: Function: ParameterValue
Main: ReturnValue

Ana işlevin (komut dosyası gövdesi) ayrıca kendi Çıkış hattı vardır ve ilk komut dosyasını CMD'den çalıştırırsak, çıktıyı bir dosyaya yönlendirirsek,

PowerShell .TestOutput1.ps1 1 > TestOutput1.txt

sonra ekranda göreceğiz

ERRORLEVEL=1

ve dosyada

Out to host.
Out to output.
ExitCode: 1
1

powershell'den benzer bir çağrı yaparsak

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

o zaman ekranda olacak

Out to host.
ExitCode: 1

ve dosyada

Out to output.
1

Bunun nedeni CMD'nin, başka talimatların yokluğunda iki iş parçacığını (Ana Bilgisayar ve Çıkış) karıştıran ve bunları aldığı her şeyi bir dosyaya gönderen CMD'ye veren powershell'i başlatmasıdır ve powershell'den başlatma durumunda, bu iki iş parçacığı ayrı ayrı mevcuttur ve simge yönlendirmeleri yalnızca Çıktıyı etkiler.

Ana konuya dönersek, powershell içindeki .NET nesne modelinin tamamen tek bir bilgisayarda (bir işletim sistemi) bulunduğunu, WSMAN aracılığıyla uzaktan kod çalıştırırken nesnelerin aktarımının XML serileştirme yoluyla gerçekleştiğini ve bunun da pek çok ek ilgi getirdiğini hatırlayalım. araştırmamıza. Aşağıdaki kodu çalıştırarak denemelerimize devam edelim:

$res=Invoke-Command -ComputerName . -ScriptBlock { &'D:sqlagentTestOutput1.ps1' $args[0] } -ArgumentList $args[0]
$res.GetType()
$host.SetShouldExit($res)

Ve ekranda gördüğümüz şey bu:

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

Harika sonuç! Bu, Invoke-Command'ı çağırırken, boru hatlarının iki iş parçacığına (Ana Bilgisayar ve Çıkış) bölünmesinin sürdürüldüğü anlamına gelir, bu da bize başarı için umut verir. Uzaktan çalıştırdığımız ilk betiği değiştireceğimiz Output akışında yalnızca bir değer bırakmaya çalışalım:

$exitcode = $args[0]
Write-Host 'Out to host.'
#Write-Output 'Out to output.'
Write-Host ('ExitCode: ' + $exitcode)
Write-Output $exitcode
$host.SetShouldExit($exitcode)

Bunu şu şekilde çalıştıralım:

$res=Invoke-Command -ComputerName . -ScriptBlock { &'D:sqlagentTestOutput1.ps1' $args[0] } -ArgumentList $args[0]
$host.SetShouldExit($res)

ve... EVET, bir zafere benziyor!

Out to host.
ExitCode: 4

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Int32                                    System.ValueType


ERRORLEVEL=4

Ne olduğunu anlamaya çalışalım. Yerel olarak powershell'i çağırdık, o da uzaktaki bilgisayarda powershell'i çağırdı ve betiğimizi orada çalıştırdı. Uzak makineden gelen iki akış (Ana Bilgisayar ve Çıkış) serileştirilip geri aktarılırken, içinde tek bir dijital değer bulunan Çıkış akışı Int32 türüne dönüştürülerek alıcı tarafa iletildi ve alıcı taraf onu kullandı. arayanın powershell'inin çıkış kodu olarak.

Ve son bir kontrol olarak SQL sunucusunda “İşletim sistemi (cmdexec)” türünde aşağıdaki metinle tek adımlı bir iş oluşturalım:

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

YAŞASIN! Görev bir hatayla tamamlandı, günlükteki metin:

Выполняется от имени пользователя: DOMAINagentuser. Out to host. ExitCode: 6.  Код завершения процесса 6.  Шаг завершился с ошибкой.

Sonuç:

  • Yazma-Çıktı kullanmaktan ve ifadeleri atama olmadan belirtmekten kaçının. Bu kodu komut dosyasında başka bir yere taşımanın beklenmeyen sonuçlara yol açabileceğini unutmayın.
  • Manuel başlatma için değil, otomasyon mekanizmalarınızda kullanılmak üzere tasarlanan komut dosyalarında, özellikle WINRM aracılığıyla uzaktan çağrılar için, Try/Catch aracılığıyla manuel hata işleme yapın ve herhangi bir olay geliştirmede bu komut dosyasının tam olarak bir temel tür değeri gönderdiğinden emin olun. . Klasik Errorlevel değerini almak istiyorsanız bu değerin sayısal olması gerekir.

Kaynak: habr.com

Yorum ekle