Handle errors in ScriptBlock in Invoke-Command Cmdlet
Asked Answered
S

4

22

I am trying to install a service on a remote machine using the powershell.

So far I have the following:

Invoke-Command -ComputerName  $remoteComputerName -ScriptBlock {
         param($password=$password,$username=$username) 
         $secpasswd = ConvertTo-SecureString $password -AsPlainText -Force
         $credentials = New-Object System.Management.Automation.PSCredential ($username, $secpasswd)
         New-Service -Name "XXX" -BinaryPathName "c:\XXX.exe" -DisplayName "XXX XXX XXX" -Description "XXXXXX." -Credential $credentials -ErrorVariable errortext 
         Write-Host("Error in: " + $errortext)
        } -ArgumentList $password,$username -ErrorVariable errortext 


Write-Host("Error out: " + $errortext)

When there is an error while executing New-Service the $errortext ErrorVariable get set properly inside the ScriptBlock, because the text: "Error in: shows me the error.

The ErrorVariable of the Invoke-Command does not get set (which I expected).

My question is:

Is it somehow possible to set the ErrorVariable of the Invoke-Command to the error I got inside the ScriptBlock?

I know I could also use InstalUtil, WMI and SC to install the service, but this is not relevant at the moment.

Smoke answered 26/9, 2012 at 11:41 Comment(0)
L
24

No, you can't get the Errorvariable from the Invoke-Command call to be set the same as in the scriptblock.

But if your goal is "detect and handle errors in the scriptblock, and also get errors returned back to the context of the Invoke-Command caller" then just do it manually:

$results = Invoke-Command -ComputerName server.contoso.com -ScriptBlock {
   try
   {
       New-Service -ErrorAction 1
   }
   catch
   {
       <log to file, do cleanup, etc>
       return $_
   }
   <do stuff that should only execute when there are no failures>
}

$results now contains the error information.

Law answered 26/9, 2012 at 20:1 Comment(1)
@latkin, you should point out in this answer that the reason this works is that you're taking a normally non-terminating error and converting it into a terminating error so that you can catch the error with a try/catch statement. It would be clearer to use the value Stop for -ErrorAction instead of 1.Toddtoddie
T
15

The Invoke-Command argument list is a one way deal. You can either output the error variable in the script e.g. on the last line of the scriptblock put:

$errortext

or better yet, just don't capture the error via the -ErrorVariable at all. The scriptblock output, including errors, will flow back to the caller even over a remote connection.

C:\> Invoke-Command -cn localhost { Get-Process xyzzy } -ErrorVariable errmsg 2>$null
C:\> $errmsg
Cannot find a process with the name "xyzzy". Verify the process name and call the cmdlet again.
    + CategoryInfo          : ObjectNotFound: (xyzzy:String) [Get-Process], ProcessCommandException
    + FullyQualifiedErrorId : NoProcessFoundForGivenName,Microsoft.PowerShell.Commands.GetProcessCommand
    + PSComputerName        : localhost

In general, I think it is much better to keep errors on the error stream, separated from the normal output.

Toddtoddie answered 26/9, 2012 at 13:44 Comment(6)
I use the ErrorVariable to check if an error has occured. After executing a command I check it and if it is not empty I write it to a file and then cancel the execution of the whole script.Smoke
But you still want to get the error information back to the caller, right? If so, you can output the error record at the end of your scriptblock.Toddtoddie
Yes, but I don't need the visual information. I need some logic where I can tell that the command has failed so I can abort the script.Smoke
In that case, check the $? right after executing Invoke-Command. That is the typical way to determine if a command failed or succeeded.Toddtoddie
I tried the $? technique and it did not work in my case. I called subinacl.exe on a bad account name inside the Invoke-Command but afterward $? still returned True. With exe calls I guess passing the exit codes back is necessary.Mcmorris
To capture multiple cmds output in scriptBlock use: -ErrorVariable +errmsgAdvertence
C
6

This is almost certainly not the "correct" answer, but this is what I use when I want Invoke-Command to throw an error in the script.

$error.Clear()
Invoke-Command -ComputerName localhost -ScriptBlock {Command-ThatFails}
if ($error.Count -gt 0) { throw $error[0] }

If you wanted to keep the error in a variable, you could do the following:

$error.Clear()
Invoke-Command -ComputerName localhost -ScriptBlock {Command-ThatFails}
if ($error.Count -gt 0) { $myErrorVariable = $error[0] }
Camouflage answered 17/4, 2019 at 19:57 Comment(1)
Don't knock it if it works, this was just the easy thing I needed, ThanksQuarta
I
0

In the strictest sense, I believe the answer is no, you cannot set Invoke-Command's ErrorVariable to the contents of the ErrorVariable inside the script block. The ErrorVariable is only for the command it's attached to.

However, you can pass the variable in the script block out to Invoke-Command's scope. In your code you run your New-Service command with -ErrorVariable errortext. Instead, create your variable in the 'script' scope by prefacing the variable name with "script:", like this: -ErrorVariable script:errortext. That makes the variable available outside of the script block as well as inside.

Now your final line Write-Host("Error out: " + $errortext) will output the error that was generated inside of the script block.

More information here and here.

Ineligible answered 27/6, 2017 at 15:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.