Previously I was running a number of remote tests on a new Powershell module to ensure it performed as expected when invoked via Powershell Remoting.
To do this I had created a new Powershell Session Configuration called 'test-config' using
New-PSSessionConfiguration
And set the StartupScript property for this configuration to a one-line script which imported my test module. This ensured the cmdlets in the module were available was soon as the session was created.
However, as soon I tried to create a new remote session I received the error
Running startup script threw an error: NEGATIVE TEST 52
+ CategoryInfo : OpenError: (System.Manageme....RemoteRunspace:RemoteRunspace) [], RemoteException
+ FullyQualifiedErrorId : PSSessionOpenFailed
This was a test exception thrown by my test module during import. What surprised me is that the exception is thrown inside a try/catch block and dealt with inside the module. It should not leaked outside or continue to propagate up the call stack.
I double checked the module code and decided to rewrite the simple startup script so it threw and handled a simple exception.
try {
throw 'handled exception'
}
catch {
Write-Warning 'caught handled exception'
}
This time when I tried to create a new remote session I got the following output:
WARNING: caught handled exception
Running startup script threw an error: handled exception
+ CategoryInfo : OpenError: (System.Manageme....RemoteRunspace:RemoteRunspace) [], RemoteException
+ FullyQualifiedErrorId : PSSessionOpenFailed
This was unexpected. The exception is clearly caught, handled and not re-thrown in the startup script. So why am I am still seeing a fatal error from Powershell reporting the exception.
Lets take a deeper look at the 'RemoteException', specifically:
$_.Exception.SerializedRemoteException.StackTrace
at System.Management.Automation.ServerRunspacePoolDriver.HandleRunspaceCreated(Object sender, RunspaceCreatedEventArgs args)
at System.EventHandler`1.Invoke(Object sender, TEventArgs e)
at System.Management.Automation.Runspaces.RunspacePool.OnRunspaceCreated(Object source, RunspaceCreatedEventArgs args)
at System.Management.Automation.Runspaces.Internal.RunspacePoolInternal.CreateRunspace()
at System.Management.Automation.Runspaces.Internal.RunspacePoolInternal.OpenHelper()
Now lets use Reflector to take a look at what's happening in the MSIL:
...
ArrayList dollarErrorVariable = (ArrayList) powershell.Runspace.GetExecutionContext.DollarErrorVariable;
if (dollarErrorVariable.Count > 0)
{
string str2 = (dollarErrorVariable[0] as ErrorRecord).ToString();
throw tracer.NewInvalidOperationException("RemotingErrorIdStrings", PSRemotingErrorId.StartupScriptThrewTerminatingError.ToString(), new object[] { str2 });
}
...
We can see that Powershell is checking the 'DollarErrorVariable' ($ERROR) and if this is greater than 0 it throws the terminating error.
There is no provision to check whether the error was caught or handled, which explains why the startup script still fails.
I strongly suspect this is a bug. However, at least we can implemented a simple workaround. We need to ensure we clear $ERROR() after we catch the exception. Of course, its important to note we must clear the variable at global scope, which might upset some Administrators. So make sure you only clear it when you need to: when being run as a startup script via remoting
try {
throw 'handled exception'
}
catch {
Write-Warning 'caught handled exception'
# remote is set if the script is being run via Remoting
if($remote -and $startup) {
$global:error.Clear()
}
}
0 comments:
Post a Comment