2016-02-04 17 views
6

我正在編寫一個執行我的構建/部署過程中的步驟之一的Powershell腳本,並且它需要在遠程計算機上運行一些操作。該腳本相對複雜,因此如果在遠程活動期間發生錯誤,我想要在腳本中發生錯誤的位置(超出已生成的日誌記錄的位置)進行詳細的堆棧跟蹤。如何在從遠程計算機上的Invoke-Command內引發的異常中保留ScriptStackTrace?

問題在於當從遠程計算機中繼終止異常時,Invoke-Command丟失堆棧跟蹤信息。如果腳本塊調用本地機器上:

Invoke-Command -ScriptBlock { 
    throw "Test Error"; 
} 

將返回所需的異常詳細信息:

Test Error 
At C:\ScriptTest\Test2.ps1:4 char:2 
+  throw "Test Error"; 
+  ~~~~~~~~~~~~~~~~~~ 
    + CategoryInfo   : OperationStopped: (Test Error:String) [], RuntimeException 
    + FullyQualifiedErrorId : Test Error 

但是,如果遠程運行:

Invoke-Command -ComputerName $remoteComputerName -ScriptBlock { 
    throw "Test Error"; 
} 

的異常堆棧跟蹤點整個Invoke-Command塊:

Test Error 
At C:\ScriptTest\Test2.ps1:3 char:1 
+ Invoke-Command -ComputerName $remoteComputerName -ScriptBlock { 
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    + CategoryInfo   : OperationStopped: (Test Error:String) [], RuntimeException 
    + FullyQualifiedErrorId : Test Error 

我可以輸送異常給手動本地機器:

$exception = Invoke-Command -ComputerName $remoteComputerName -ScriptBlock { 
    try 
    { 
     throw "Test Error"; 
    } 
    catch 
    { 
     return $_; 
    } 
} 

throw $exception; 

但重新把它扔失去堆棧跟蹤:

Test Error 
At C:\ScriptTest\Test2.ps1:14 char:1 
+ throw $exception; 
+ ~~~~~~~~~~~~~~~~ 
    + CategoryInfo   : OperationStopped: (Test Error:PSObject) [], RuntimeException 
    + FullyQualifiedErrorId : Test Error 

如果我寫的異常輸出:

$exception = Invoke-Command -ComputerName $remoteComputerName -ScriptBlock { 
    try 
    { 
     throw "Test Error"; 
    } 
    catch 
    { 
     return $_; 
    } 
} 

Write-Output $exception; 

我得到正確的堆棧跟蹤信息:

Test Error 
At line:4 char:3 
+   throw "Test Error"; 
+   ~~~~~~~~~~~~~~~~~~ 
    + CategoryInfo   : OperationStopped: (Test Error:String) [], RuntimeException 
    + FullyQualifiedErrorId : Test Error 

但是,由於它不在錯誤流上,所以我的構建工具沒有正確拾取它。如果我嘗試寫入錯誤,我有類似的問題重新拋出異常和堆棧跟蹤指向腳本的錯誤部分。

所以我的問題是 - 我如何讓Powershell從遠程機器報告異常,就好像它已在本地提出的一樣,具有相同的堆棧跟蹤信息和錯誤流?

+1

使用類似'Invoke-Command -ErrorVariable remoteerror'的東西,那麼結果錯誤流的lts應該在'$ remoteerror'中? – Matt

+0

我把它收回'-ErrorVariable'不應該包含你需要的信息。我想我有一個想法想用'throw' – Matt

+0

[本文](https://blogs.msdn.microsoft.com/powershell/2009/07/13/advanced-debugging-in-powershell/)上的任何內容對您有幫助? 「對於調試,而不是Invoke-Command,使用Enter-PSSession可能會更好,因此您可以在遠程計算機上有一個shell來嘗試輸出」 – user4317867

回答

3

當你運行一些代碼,它失敗,您會收到一個ErrorRecord反映你(本地計算機)執行的代碼。因此,當您使用throw "error"時,您可以訪問該代碼的invocationinfo和異常。

當您使用Invoke-Command時,您不再執行throw "error",遠程計算機是。你(本地計算機)正在執行Invoke-Command ....,這就是爲什麼你得到的ErrorRecord反映(而不是你想要的真正的例外)。這是必須的方式,因爲異常可能來自遠程計算器執行的腳本塊,但它本身可能是Invoke-Command本身的異常,因爲它無法連接到遠程計算機或類似的東西。

當異常最初在遠程計算機上拋出時,Invoke-Command/PowerShell在本地計算機上拋出RemoteException

#Generate errors 
try { Invoke-Command -ComputerName localhost -ScriptBlock { throw "error" } } 
catch { $remoteexception = $_ } 

try { throw "error" } 
catch { $localexception = $_ } 

#Get exeception-types 
$localexception.Exception.GetType().Name 
RuntimeException 

$remoteexception.Exception.GetType().Name 
RemoteException 

此異常型有一些額外的屬性,包括SerializedRemoteExceptionSerializedRemoteInvocationInfo其中包含從在遠程會話拋出的異常的信息。使用這些,您可以收到「內部」異常。

樣品:

#Show command that threw error 
$localexception.InvocationInfo.PositionMessage 

At line:4 char:7 
+ try { throw "error" } 
+  ~~~~~~~~~~~~~ 

$remoteexception.Exception.SerializedRemoteInvocationInfo.PositionMessage  

At line:1 char:2 
+ throw "error" 
+ ~~~~~~~~~~~~~ 

然後,您可以編寫一個簡單的函數來動態地提取信息,例如:

function getExceptionInvocationInfo ($ex) { 
    if($ex.Exception -is [System.Management.Automation.RemoteException]) { 
     $ex.Exception.SerializedRemoteInvocationInfo.PositionMessage 
    } else { 
     $ex.InvocationInfo.PositionMessage 
    } 
} 

function getException ($ex) { 
    if($ex.Exception -is [System.Management.Automation.RemoteException]) { 
     $ex.Exception.SerializedRemoteException 
    } else { 
     $ex.Exception 
    } 
} 

getExceptionInvocationInfo $localexception 

At line:4 char:7 
+ try { throw "error" } 
+  ~~~~~~~~~~~~~ 

getExceptionInvocationInfo $remoteexception 
At line:1 char:2 
+ throw "error" 
+ ~~~~~~~~~~~~~ 

請注意,SerializedRemoteExpcetion顯示爲PSObject因爲系列化/網絡傳輸期間的反序列化,所以如果您要檢查異常類型,您需要從psobject.TypeNames中提取它。

$localexception.Exception.GetType().FullName 
System.Management.Automation.ItemNotFoundException 

$remoteexception.Exception.SerializedRemoteException.GetType().FullName 
System.Management.Automation.PSObject 

#Get TypeName from psobject 
$remoteexception.Exception.SerializedRemoteException.psobject.TypeNames[0] 
Deserialized.System.Management.Automation.ItemNotFoundException 
+1

這真的很不錯。工作很好。 – Matt

+0

謝謝。你也是。 :) –

+0

太好了 - 這給我所有來自我需要的例外的信息。謝謝! – FacticiusVir

3

我相信有更多經驗的人可以幫忙,但我想給你一些東西在同時咀嚼。聽起來像你想要使用throw,因爲你正在尋找終止異常。 Write-Error會寫入錯誤流,但不會終止。無論您的選擇如何,我的建議仍然是一樣的。

捕獲異常到一個變量是一個好的開始爲這個,所以我會從你的榜樣推薦此塊:在這種情況下

$exception = Invoke-Command -ComputerName $remoteComputerName -ScriptBlock { 
    try 
    { 
     throw "Test Error"; 
    } 
    catch 
    { 
     return $_; 
    } 
} 

$exception應該是一個Deserialized.System.Management.Automation.ErrorRecord。您可以將自定義對象發送到throw ...

您還可以引發ErrorRecord對象或Microsoft .NET Framework異常。

但在這種情況下,它不工作,這可能是由於反序列化。有一次,我試圖創建自己的錯誤對象,但一些所需的屬性只讀,所以我跳過了。

PS M:\Scripts> throw $exception 
Test Error 
At line:1 char:1 
+ throw $return 
+ ~~~~~~~~~~~~~ 
    + CategoryInfo   : OperationStopped: (Test Error:PSObject) [], RuntimeException 
    + FullyQualifiedErrorId : Test Error 

但是,只要寫入輸出流,就可以爲您提供正確的信息,如您所見。

PS M:\Scripts> $exception 
Test Error 
At line:4 char:9 
+   throw "Test Error"; 
+   ~~~~~~~~~~~~~~~~~~ 
    + CategoryInfo   : OperationStopped: (Test Error:String) [], RuntimeException 
    + FullyQualifiedErrorId : Test Error 

$exception是一個對象將包含所有的這些信息,以便一個可行的辦法屬性來得到你需要的是從所期望的特性使自定義錯誤字符串的內容。然而,結果證明這是值得的更多的工作。一個簡單的折衷辦法是使用Out-String來轉換有用的輸出,以便它可以作爲錯誤返回。

PS M:\Scripts> throw ($return | out-string) 
Test Error 
At line:4 char:9 
+   throw "Test Error"; 
+   ~~~~~~~~~~~~~~~~~~ 
    + CategoryInfo   : OperationStopped: (Test Error:String) [], RuntimeException 
    + FullyQualifiedErrorId : Test Error 

At line:1 char:1 
+ throw ($return | out-string) 
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    + CategoryInfo   : OperationStopped: (Test Error 
At ...Test Error 

:String) [], RuntimeException 
    + FullyQualifiedErrorId : Test Error 
At line:4 char:9 
+   throw "Test Error"; 
+   ~~~~~~~~~~~~~~~~~~ 
    + CategoryInfo   : OperationStopped: (Test Error:String) [], RuntimeException 
    + FullyQualifiedErrorId : Test Error 

所以,現在我們有從相對於腳本塊,並在那裏調用代碼生成錯誤信息都正確的終止錯誤。你會看到一些重複,但也許這將是你的開始。

如果您有其他需要特別說明的信息,我會深入研究$exception對象Get-Member,看看您是否可以找到您正在尋找的具體內容。一些顯着的性能這裏將

$exception.ErrorCategory_Reason 
$exception.PSComputerName 
$exception.InvocationInfo.Line 
$exception.InvocationInfo.CommandOrigin 

最後一個會讀出這樣的事情。

PS M:\Scripts> $exception.InvocationInfo.PositionMessage 
At line:4 char:9 
+   throw "Test Error"; 
+   ~~~~~~~~~~~~~~~~~~ 
+0

感謝您對此進行更多的挖掘 - 看起來我必須使用這種方法(堆疊的異常消息),並結合Frode F.的答案中的序列化信息來創建信息性日誌條目。 – FacticiusVir

0

希望有更多經驗的人可以對此發表評論,我的評論似乎已經被忽略了。

我發現this article其中提供了一些洞察到使用Enter-PSSession

甚至更​​好,創建持久會話

$session = New-PSSession localhost 
Invoke-Command $session {ping example.com} 
Invoke-Command $session {$LASTEXITCODE} 

要做到使用跟蹤調試,記住,你需要設置VerbosePreference,DebugPreference等在遠程會話

Invoke-Command $session {$VerbosePreference = ‘continue’} 
Invoke-Command $session {Write-Verbose hi} 
+1

他想訪問'Invoke-Command'中使用的scriptblock內部引發的異常。使用會話與計算機名稱相同的cmdlet不會改變異常傳遞到「管理計算機」的方式。 –

+0

碰巧,我正在使用的實際腳本確實使用了使用New-PSSession創建的會話,但由於該部分非常複雜(大量跨進程的斷開連接和重新連接),並且在這種情況下似乎沒有影響異常處理,我從示例代碼中省略了它。不管怎麼說,還是要謝謝你! – FacticiusVir