Start-DscConfiguration doesn't throw exceptions? - powershell

I noticed that if applying a configuration through Start-DscConfiguration fails, it writes to the error stream but doesn't
throw an Exception? That is, if I do the following:
try{
Start-DscConfiguration -Path ".\MyConfig" -Wait -Verbose
}catch{
#...
}
...it never ends up in the catch handler. I suspect this may have something to do with the fact that without the "-Wait",
Start-DscConfiguration starts an async job for this, and async commands probably don't throw exceptions, but in a synchronous
scenario, I would very much like to know if my configuration could be applied.
What is the proper way to determine if Start-DscConfiguration has completed succesfully?

The only way I know is to check the global "$error" variable and compare the number of error records before and after your call to Start-DscConfiguration. If there's more afterwards then something must have gone wrong during the call, so throw your own exception:
Configuration TestErrorHandling {
Node "localhost" {
Script ErroringResource {
GetScript = { return $null; }
TestScript = { return $false; }
SetScript = { throw new-object System.InvalidOperationException; }
}
}
}
$errorCount = $error.Count;
write-host "starting dsc configuration"
$mof = TestErrorHandling;
Start-DscConfiguration TestErrorHandling –Wait –Verbose;
write-host "dsc configuration finished"
if( $error.Count -gt $errorCount )
{
$dscErrors = $error[$errorCount..($error.Count - 1)];
write-host "the following errors occurred during dsc configuration";
write-host ($dscErrors | fl * | out-string);
throw $dscErrors[-1];
}

There's another way to make it cause an exception. Try saving it into the ErrorVariable like this :
try
{
Start-DscConfiguration -Path ".\MyConfig" -Wait -Verbose -ErrorVariable ev
}
catch
{
$myException = $_
}
Weirdly so, this throws the exception when there's an error (which is what you wanted). You can get the value of your exception in the $myexception variable, and also could get just a one liner description of your error using $ev
PS: Note that while mentioning ev in the errorVariable parameter, you do it without the '$' symbol - since you're only specifying the variable 'name'.

Start-DscConfiguration when used without -Wait will create a job object - with one child job for every computername. PowerShell job objects have an Error stream which contains all the errors. You can check this stream as well
$job = Start-DscConfiguration -Force -Verbose -Path C:\Temp\Demo\ -ComputerName localhost
Receive-Job $job -Wait
'Errors in job = ' + ($job.childjobs[0].Error.Count)

Related

It skips it-statement when executing function in Pester, but shouldn't

I am trying to test a Function in an script file that is testing the connection to a PC. I am trying to do that by calling the mock "Test-Connection" from an other Script in an Pester Test.
When I run Temp.Testing.ps1
describe 'Test Error Handling' {
$myDir = Split-Path -parent $PSCommandPath
$testFile = "$myDir\TryToRenameComputer.ps1"
.$testFile
mock 'Test-Connection' { $false }
$pathForLogs = "C:\temp\Logs.txt"
it 'exits if Test-Connection Failed'{
TryToRenameComputer -OldName "OldName"
Assert-MockCalled 'Test-Connection' -Times 1 -Scope It
}
}
TryToRenameComputer.ps1
function TryToRenameComputer {
param([parameter(Mandatory=$true)]
[string]$computerName)
if (!(Test-Connection -ComputerName $computerName -Quiet)) {
exit
}
}
it skips the it-statement and shows me no errors. Only "Describing Test Error Handling".
Expected Result:
Actual Result:
I already tried running a other Function and it worked.
Also when I am running multiple it-statements all get skipped when I call the Function in 1 it-statement.
I also tried rewriting it so it is no function anymore and it worked.
The problem is likely occurring because of the exit statement in your code. This is getting executed because in the if statement before it you are using -not via its shorthand of ! to test for the result of Test-Connection and because your Mock sets Test-Connection to $false.
By using exit you are immediately terminating the PowerShell host, which is then stopping your tests from executing/completing.
Instead of using exit consider using break or return to stop the execution of the function without terminating the script. Or if you potentially do want to terminate the script, consider using throw as you can then stop the parent script when an exception has occurred.
You could then modify your tests to test for that throw as that is the result you expect to occur when test-connection returns $false. For example:
function TryToRenameComputer {
param([parameter(Mandatory=$true)]
[string]$computerName)
if (!(Test-Connection -ComputerName $computerName -Quiet)) {
Thow "Could not connect to $computerName"
}
}
describe 'Test Error Handling' {
$myDir = Split-Path -parent $PSCommandPath
$testFile = "$myDir\TryToRenameComputer.ps1"
.$testFile
mock 'Test-Connection' { $false }
$pathForLogs = "C:\temp\Logs.txt"
it 'Throws an error if Test-Connection Failed'{
{ TryToRenameComputer -OldName "OldName" } | Should -Throw
Assert-MockCalled 'Test-Connection' -Times 1 -Scope It
}
}
There's no easy way to handle exit in your tests and its generally a bit of an anti-pattern when you're writing code for automation.

Get fail message from a function on powershell script

I am not quite sure how to explain my problem, but I have a function that installs Office, imagine the person that runs this script does not have internet connection or does not have enough space on her hard drive. I have the XML file set to hide the setup interface so the user can't see the installation process. Just to be clear all my code works fine, just want add this feature so that if something goes wrong while the user runs the script I know where the error was.
This is my function:
Function Install-Office365OfficeProducts{
Write-Host ""
Start-Sleep -Seconds 5
Write-Host "Installing Office 365 ProPlus..."
# Installing Office 365 ProPlus
Install-Office365Product -path "$PSScriptRoot\setup.exe" -xmlPath "$PSScriptRoot\InstallO365.xml"
This is what I have tried:
if (Install-Office365OfficeProducts -eq 0) {
Write-Host "FAILED"}
I am very confused, I thought that a function that runs with no error returns 1 and when it runs with errors returns 0.
Also have tried to put the code like this:
try {
Install-Office365Product -path "$PSScriptRoot\setup.exe" -xmlPath "$PSScriptRoot\InstallO365.xml"
} catch {
Write-Host "Failed!"
}
EDIT:
Basically i want to be shown an error if the Office setup is not finished...
#Thomas
Function Install-Office365Product{
Param (
[string]$path,
[string]$xmlPath
)
$arguments = "/configure `"$xmlPath`""
try{
Start-Process -FilePath "$path" -ArgumentList "$arguments" -Wait -NoNewWindow -ErrorAction Stop
}catch{
Write-Host "It was not possible to install the product!"
}
}
Your try/catch-block inside Install-Office365OfficeProducts is useless, because Install-Office365Product will not throw anything, except you pass wrong arguments. The try/catch-block inside Install-Office365Product will most likely also not catch anything. But you can of course evaluate the return code of your installer called with Start-Process:
function Install-Office365Product {
Param (
[string]$path,
[string]$xmlPath
)
$arguments = "/configure `"$xmlPath`""
$process = Start-Process -FilePath "$path" -ArgumentList "$arguments" -Wait -PassThru -NoNewWindow
if ($process.ExitCode -eq 0) {
Write-Host "Installation successful"
} else {
Write-Host "Installation failed"
}
}
Instead of writing to stdout, you can of course also throw an exception and handle it later in a higher function.

Catching errors in Powershell

I have weird problem, when im using try/catch method for some cmdlets its working for some not.
Can you advice on that?
This one is working fine:
try
{
$LookingForRemoteMailboxOnPrem = Get-RemoteMailbox $info -ErrorAction Stop | select -ExpandProperty UserPrincipalName
}
catch
{
string]$t = $Error[0]
}
But this one is not:
try
{
$EnableRemoteMailbox = Enable-RemoteMailbox $info -RemoteRoutingAddress $remote -PrimarySmtpAddress $info2 -ErrorAction Stop
}
catch
{
[string]$t = $Error[0]
}
Not saving error to $t variable
The $ErrorActionPreference is set to Continue by default. This means if PowerShell can "recover" from an error it won't throw an exception. You can use the -ErrorAction parameter to change the behaviour at every cmdlet.
This link gives a good example:
Try {dir c:\missingFolder}
Catch [System.Exception] {"Caught the exception"}
Finally {$error.Clear() ; "errors cleared"}
The string "Caught the exception does not occur in PowerShell windows. If you set the -ErrorAction to Stop an exception is raised.
Details are described here.

Try/catch does not seem to have an effect

I am new to powershell, and I am trying to add error handling via try/catch statements, but they don't seem to actually be catching the error. This is powershell v2 CP3.
$objComputer = $objResult.Properties;
$strComputerName = $objComputer.name
write-host "Checking machine: " $strComputerName
try
{
$colItems = get-wmiobject -class "Win32_PhysicalMemory" -namespace "root\CIMV2" -computername $strComputerName -Credential $credentials
foreach ($objItem in $colItems)
{
write-host "Bank Label: " $objItem.BankLabel
write-host "Capacity: " ($objItem.Capacity / 1024 / 1024)
write-host "Caption: " $objItem.Caption
write-host "Creation Class Name: " $objItem.CreationClassName
write-host
}
}
Catch
{
write-host "Failed to get data from machine (Error:" $_.Exception.Message ")"
write-host
}
finally
{ }
When it fails to contact a specific machine, I get this in console, and not my clean catch message:
Get-WmiObject : The RPC server is
unavailable. (Exception from HRESULT:
0x800706BA) At Z:\7.0 Intern
Programvare\Powershell\Get memory of
all computers in AD.ps1:25 char:34
+ $colItems = get-wmiobject <<<< -class "Win32_PhysicalMemory"
-namespace "root\CIMV2" -computername $strComputerName -Credential
$credentials
+ CategoryInfo : InvalidOperation: (:) [Get-WmiObject],
COMException
+ FullyQualifiedErrorId : GetWMICOMException,Microsoft.PowerShell.Commands.GetWmiObjectCommand
I was able to duplicate your result when trying to run a remote WMI query. The exception thrown is not caught by the Try/Catch, nor will a Trap catch it, since it is not a "terminating error". In PowerShell, there are terminating errors and non-terminating errors . It appears that Try/Catch/Finally and Trap only works with terminating errors.
It is logged to the $error automatic variable and you can test for these type of non-terminating errors by looking at the $? automatic variable, which will let you know if the last operation succeeded ($true) or failed ($false).
From the appearance of the error generated, it appears that the error is returned and not wrapped in a catchable exception. Below is a trace of the error generated.
PS C:\scripts\PowerShell> Trace-Command -Name errorrecord -Expression {Get-WmiObject win32_bios -ComputerName HostThatIsNotThere} -PSHost
DEBUG: InternalCommand Information: 0 : Constructor Enter Ctor
Microsoft.PowerShell.Commands.GetWmiObjectCommand: 25857563
DEBUG: InternalCommand Information: 0 : Constructor Leave Ctor
Microsoft.PowerShell.Commands.GetWmiObjectCommand: 25857563
DEBUG: ErrorRecord Information: 0 : Constructor Enter Ctor
System.Management.Automation.ErrorRecord: 19621801 exception =
System.Runtime.InteropServices.COMException (0x800706BA): The RPC
server is unavailable. (Exception from HRESULT: 0x800706BA)
at
System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo)
at System.Management.ManagementScope.InitializeGuts(Object o)
at System.Management.ManagementScope.Initialize()
at System.Management.ManagementObjectSearcher.Initialize()
at System.Management.ManagementObjectSearcher.Get()
at Microsoft.PowerShell.Commands.GetWmiObjectCommand.BeginProcessing()
errorId = GetWMICOMException errorCategory = InvalidOperation
targetObject =
DEBUG: ErrorRecord Information: 0 : Constructor Leave Ctor
System.Management.Automation.ErrorRecord: 19621801
A work around for your code could be:
try
{
$colItems = get-wmiobject -class "Win32_PhysicalMemory" -namespace "root\CIMV2" -computername $strComputerName -Credential $credentials
if ($?)
{
foreach ($objItem in $colItems)
{
write-host "Bank Label: " $objItem.BankLabel
write-host "Capacity: " ($objItem.Capacity / 1024 / 1024)
write-host "Caption: " $objItem.Caption
write-host "Creation Class Name: " $objItem.CreationClassName
write-host
}
}
else
{
throw $error[0].Exception
}
If you want try/catch to work for all errors (not just the terminating errors) you can manually make all errors terminating by setting the ErrorActionPreference.
try {
$ErrorActionPreference = "Stop"; #Make all errors terminating
get-item filethatdoesntexist; # normally non-terminating
write-host "You won't hit me";
} catch{
Write-Host "Caught the exception";
Write-Host $Error[0].Exception;
}finally{
$ErrorActionPreference = "Continue"; #Reset the error action pref to default
}
Alternatively... you can make your own try/catch function that accepts scriptblocks so that your try/catch calls are not as kludge. I have mine return true/false just in case I need to check if there was an error... but it doesn't have to. Also, exception logging is optional, and can be taken care of in the catch, but I found myself always calling the logging function in the catch block, so I added it to the try/catch function.
function log([System.String] $text){write-host $text;}
function logException{
log "Logging current exception.";
log $Error[0].Exception;
}
function mytrycatch ([System.Management.Automation.ScriptBlock] $try,
[System.Management.Automation.ScriptBlock] $catch,
[System.Management.Automation.ScriptBlock] $finally = $({})){
# Make all errors terminating exceptions.
$ErrorActionPreference = "Stop";
# Set the trap
trap [System.Exception]{
# Log the exception.
logException;
# Execute the catch statement
& $catch;
# Execute the finally statement
& $finally
# There was an exception, return false
return $false;
}
# Execute the scriptblock
& $try;
# Execute the finally statement
& $finally
# The following statement was hit.. so there were no errors with the scriptblock
return $true;
}
#execute your own try catch
mytrycatch {
gi filethatdoesnotexist; #normally non-terminating
write-host "You won't hit me."
} {
Write-Host "Caught the exception";
}
It is also possible to set the error action preference on individual cmdlets, not just for the whole script. This is done using the parameter ErrorAction (alisa EA) which is available on all cmdlets.
Example
try
{
Write-Host $ErrorActionPreference; #Check setting for ErrorAction - the default is normally Continue
get-item filethatdoesntexist; # Normally generates non-terminating exception so not caught
write-host "You will hit me as exception from line above is non-terminating";
get-item filethatdoesntexist -ErrorAction Stop; #Now ErrorAction parameter with value Stop causes exception to be caught
write-host "you won't reach me as exception is now caught";
}
catch
{
Write-Host "Caught the exception";
Write-Host $Error[0].Exception;
}
This is my solution. When Set-Location fails it throws a non-terminating error which is not seen by the catch block. Adding -ErrorAction Stop is the easiest way around this.
try {
Set-Location "$YourPath" -ErrorAction Stop;
} catch {
Write-Host "Exception has been caught";
}
Adding "-EA Stop" solved this for me.
Edit: As stated in the comments, the following solution applies to PowerShell V1 only.
See this blog post on "Technical Adventures of Adam Weigert" for details on how to implement this.
Example usage (copy/paste from Adam Weigert's blog):
Try {
echo " ::Do some work..."
echo " ::Try divide by zero: $(0/0)"
} -Catch {
echo " ::Cannot handle the error (will rethrow): $_"
#throw $_
} -Finally {
echo " ::Cleanup resources..."
}
Otherwise you'll have to use exception trapping.
In my case, it was because I was only catching specific types of exceptions:
try
{
get-item -Force -LiteralPath $Path -ErrorAction Stop
#if file exists
if ($Path -like '\\*') {$fileType = 'n'} #Network
elseif ($Path -like '?:\*') {$fileType = 'l'} #Local
else {$fileType = 'u'} #Unknown File Type
}
catch [System.UnauthorizedAccessException] {$fileType = 'i'} #Inaccessible
catch [System.Management.Automation.ItemNotFoundException]{$fileType = 'x'} #Doesn't Exist
Added these to handle additional the exception causing the terminating error, as well as unexpected exceptions
catch [System.Management.Automation.DriveNotFoundException]{$fileType = 'x'} #Doesn't Exist
catch {$fileType='u'} #Unknown

PowerShell doesn't return error running executable [duplicate]

I'm new to powershell, but I'm trying to output some simple logging in a ps I'm writing to create scheduled tasks. My code is below. It seems that it doesn't throw an exception when you get an error with schtasks. Another SO question mentioned this with fileIO actions and suggested doing "-ea stop" but that doesn't work with schtasks.
#create log file
$log = "\\servername\!incoming\Deploy\log.txt"
Clear-Content $log
#get input file list
$txtServerList = Gc "\\servername\!incoming\Deploy\serverlist.txt"
#loop through each server
ForEach($strServername In $txtServerList)
{
try
{
#install task from XML template
schtasks /create /s $strServername /tn InventoryServer /XML "\\servername\!incoming\Deploy\TaskTemplate.xml"
#run the task immediately
schtasks /run /s $strServername /tn InventoryServer
}
catch [exception]
{
Add-Content -path $log -value $strServername
#Add-Content -path $log -value $_.Exception
#Add-Content -path $log -value $_.Exception.Message
#Add-Content -path $log -value ""
}
}
I verified that 'Add-Content -path "\servername!incoming\Deploy\log.txt" -value "test"'works, so like I said I'm fairly sure it's just not throwing an exception.
In order for a Try/Catch to work, PowerShell needs a terminating exception. When running a cmdlet in a Try block you can make that happen by using -erroraction Stop (or use the -ea alias). As you already realize SCHTASKS.EXE can't do this. Without a terminating exception, the code in the Catch block will never run.
What you have to do is step out side the box, so to speak, and independently check if Schtasks failed. If so, they you can use Write-Error in your Try block.
One thing you might try is using Start-Process and look at the exit code. Anything other than 0 should be an error.
Try {
get-date
$p=Start-Process schtasks.exe -ArgumentList "/Create foo" -wait -passthru
if ($p.exitcode -ne 0) {
write-error "I failed with error $($p.exitcode)"
}
}
Catch {
"oops"
$_.Exception
}