Powershell -ErrorAction SilentlyContinue not adding error to $Error variable - powershell

I am hoping for a bit of help on an issue I'm having where once I add -ErrorAction SilentlyContinue to my command, no errors are written to the $Error variable. When I remove the ErrorAction, the command works perfectly fine and I can see the errors in the $Error variable but the errors are also printed to the console, which I don't want. Once I add the ErrorAction, the command still works but does not write any errors to the $Error variable.
Below is the function that contains the command.
function removebutton{
Try{
Write-Host "Please Wait..."
Import-CSV $WPFfile_path.Text | foreach{Remove-DistributionGroupMember -Identity $WPFlist_email.Text -Member $_.email -Confirm:$false -ErrorAction SilentlyContinue}
Write-Host("Completed Processing List")
[System.Windows.MessageBox]::Show("Completed Processing List")
If ($error -ne $null)
{
Write-Host -ForegroundColor Red ($Error | Select-Object Exception -ExpandProperty Exception | Out-String)
[System.Windows.MessageBox]::Show(($Error | Select-Object Exception -ExpandProperty Exception | Out-String))
}
Else
{
}
Get-DistributiongroupMember $WPFlist_email.Text | Select-Object DisplayName, PrimarySMTPAddress | Out-GridView
}
Catch{
[System.Windows.MessageBox]::Show(($Error[0]))
}
}
Any help will be greatly appreciated!
Kind regards,
Dust
When I remove the ErrorAction, the command works perfectly fine and I can see the errors in the $Error variable but the errors are also printed to the console, which I don't want. Once I add the ErrorAction, the command still works but does not write any errors to the $Error variable.

Note:
The following describes PowerShell's normal behavior.
As it turns out, an apparent bug in Remove-DistributionGroupMember prevents non-terminating errors from being recorded in $Error with -ErrorAction SilentlyContinue present, which - for reasons unknown - can be bypassed by using 2>$null to silence error output instead.
-ErrorAction SilentlyContinue continue does record non-terminating errors in the automatic $Error variable (it is only -ErrorAction Ignore that doesn't).
Do not use If ($Error -ne $null) to test if an error occurred in the most recent statement, given that $Error contains all errors that have occurred in the session so far.
As an aside: To test a value for $null, place it on the LHS of -eq / -ne, given that these operators act as filters with collections (arrays) as the LHS - see the docs.
Instead, use the automatic $? variable: if (-not $?)
Alternatively, you could run $Error.Clear() before the statement of interest, which would then allow you to use if ($Error.Count -gt 0), but that comes at the expense of wiping out the session-level history of errors.
The best option may be to use the common -ErrorVariable parameter, which allows you to collect a given call's non-terminating errors in a self-chosen variable (which must be specified without $; e.g., -ErrorVariable errs in order to collect errors in $errs); note that -ErrorAction SilentlyContinue (or redirection 2>$null) is still also needed to prevent error output.
However, non-terminating errors - which is what the common -ErrorAction parameter exclusively acts on - do not trigger the catch block of try ... catch ... finally statements - only terminating errors do.
You can promote non-terminating errors to terminating errors by using -ErrorAction Stop, causing them to trigger the catch block too.
Note that, in a catch script block, you can more simply refer to the triggering error via the automatic $_ variable; that is, instead of $Error[0], you can use $_.
For a comprehensive overview of PowerShell's bewilderingly complex error handling, see GitHub docs issue #1583.

Amazingly, replacing -ErrorAction SilentlyContinue with 2>$null resolved my issue. The errors are no longer written to console and only written to the $Error variable.
Thank you #mklement0 for your help! Much appreciated! I have bought you a coffee to say thanks!

Related

Powershell Get-Service (Stop) but NOT display status "waiting for <x> service to stop..."

Basically, I don't have an issue with the action being taken. My question is about the status output of the action "waiting for..." message. Is there a way to suppress that message?
Example:
PS C:\Users\a.real.person.maybe> Get-Service *fewserviceswithmatchingprefixes* | Where-Object {$_.Name -notmatch 'somethinghere' -and $_.Name -ne 'alsosomethinghere' -and $_.Name -ne 'somethinghereprobably'} | Stop-Service
WARNING: Waiting for service 'thoseservicesabove' to stop...
WARNING: Waiting for service 'anotheroneofthoseservicesabove' to stop...
You can either set the value of the $WarningActionPreference variable at the callsite to Ignore or SilentlyContinue (both will suppress consumption and rendering of the warning message):
$WarningActionPreference = 'SilentlyContinue'
Get-Service *fewserviceswithmatchingprefixes* | Where-Object {$_.Name -notmatch 'somethinghere' -and $_.Name -ne 'alsosomethinghere' -and $_.Name -ne 'somethinghereprobably'} | Stop-Service
Or you can use the -WarningAction common parameter explicitly when making the call to Stop-Service (it will only affect that particular invocation of Stop-Service):
... | Stop-Service -WarningAction SilentlyContinue
#mathias in comments hit the nail on the head!
bunch of stuff | Stop-Service -WarningAction SilentlyContinue
To complement Mathias' helpful answer:
You can alternatively use a 3> redirection, namely 3>$null to silence warnings:
... | Stop-Service 3>$null
PowerShell's output streams are numbered (with streams 1 (success), and 2 (error) corresponding to the system-level stdout and stderr streams when communicating with the outside world), and 3 refers to the warning stream, which can be targeted >, the redirection operator; redirecting to $null effectively discards the targeted stream's output, if any.
This works with any of the output streams (> being implicitly the same as 1>, i.e. targeting the success output stream), and PowerShell even offers redirection *> to redirect all streams.
Targeting a file name or path rather than $null (quietly) saves the stream output to a plain-text file with the same formatting you would see in the terminal, except that stream-specific prefixes such as "WARNING: " are omitted.
Using something like 3> has one advantage over using the common -WarningAction parameter:
Only cmdlets and advanced functions and scripts support -WarningAction, whereas 3>$null is also effective with simple functions and scripts that use Write-Warning.
By contrast, the $WarningPreference preference variable is equally effective for all PowerShell commands.
However, preference variables, notably the $ErrorActionPreference preference variable, do not apply to external programs: streams 3 and higher do not apply to external programs, and an external program's stderr output is (sensibly) not considered error output by default (though such output can be targeted as stream number 2); to silence an external program's stderr output, you must use 2>$null
As an aside: Additional warning-related functionality that is also only supported by cmdlets and advanced functions/scripts is the ability to collect warnings in a variable, via the common -WarningVariable parameter; the closest approximation of this functionality with a 3> redirection is to target a file to write the warnings to (which would necessitate displaying that file's content after the fact to also print the warnings).
While technically distinct, a stream-targeted redirection such as 3> should behave the same as the equivalent -WarningAction SilentlyContinue parameter, and - with the exception of 2> / -ErrorAction - equally applies to -*Action Ignore.
-ErrorAction Ignore -like 2>$null / -ErrorAction SilentlyContinue - suppresses error-stream output, but - unlike the latter - additionally prevents the (non-terminating) errors that occur from getting recorded in the automatic $Error variable, which is a session-wide collection of errors that have occurred so far.
In rare edge cases (for reasons unknown to me), -ErrorAction SilentlyContinue can fail to record $Error, whereas 2>$null still does - see this answer for an example.

How to wait for permission denied type erros over the network

In a script, I use Get-ChildItem -File -Recurse in order to check my permissions on subfolders. Then, I try to read the first character of every files. My goal is to catch Permission Denied type errors using $Error. It works well locally. But when I execute the script on a remote server with a UNC long path, the errors aren't generated. If I run manually the Get-ChildItem command just after the execution of the script, which is supposed to generate some errors, it display the files but does not generate errors. If I wait a few minutes and I run it again, I finally get the errors displayed.
Is there a way to wait for the errors to be generated?
Here is the specific part of my code which doesn't generate any error over the network:
# Check if the current item is a folder or a file
If($elem.Attributes -eq 'Directory')
{
# Get all child items of File type
$subElem = Get-ChildItem -LiteralPath $elem.FullName -File -Recurse -ErrorAction SilentlyContinue
# Parse subfolders and files to check permissions integrity. To generate an Permission Denied error, a file must be open
ForEach($subItem in $subElem)
{
# Read the first character of the current sub-item
Get-Content -LiteralPath $subItem.FullName -Encoding byte -TotalCount 1 -ErrorAction SilentlyContinue | Out-Null
}
}
Else
{
# Read the first character of the current element
Get-Content -LiteralPath $elem.FullName -Encoding byte -TotalCount 1 -ErrorAction SilentlyContinue | Out-Null
}
I finally found the solution myself.
In this script, I use the module NTFSSecurity (https://github.com/raandree/NTFSSecurity) in order to manage ACLs and inheritance. Over the network, it seems to be a bit slow.
Before the bit of code I shared above, I have a few lines which check and updates a bunch of ACLs over the network. As it takes some time, errors are only generated some time after. In this case, if the command encounter an error, it just continues but doesn't display or catch the error at the same time.
I used errors to detect items on which I had to recover the access. Now, I use another cmdlet coming with the NTFSSecurity module, Get-NTFSEffectiveAccess.
I wrote a little function which does perfectly the trick:
Function Check-MyAccess([String]$Path)
{
# Get effective permissions
$effectiveAccess = Get-NTFSEffectiveAccess -Path $Path -ErrorAction SilentlyContinue
# Check to be, at least, able to read the item
If(($effectiveAccess -eq $Null) -or ($effectiveAccess.AccessRights -Match 'Synchronize') -or ((($effectiveAccess.AccessRights -Like '*Read*') -or ($effectiveAccess.AccessRights -Like '*Modify*') -and ($effectiveAccess.AccessControlType -Match 'Deny'))))
{
Return $False
}
Else
{
Return $True
}
}

Skip part of script on error

I've been banging my head against the wall for awhile on this one. No amount of Googling has yielded me any successful results thus far. Wondering if someone can give me a hand?
# Load the PowerShell module for Active Directory
Import-Module ActiveDirectory
# For each computer in AD that is a member of the target group
Get-ADGroupMember -Identity "CN=RenameComputer,CN=Users,DC=int,DC=example,DC=com" | ForEach {
# Define the new name as the old one, minus one letter at the end
$NewComputerName = $_.Name -replace ".$"
# Perform the rename operation
Rename-Computer -ComputerName $_.Name -NewName $NewComputerName -Force -PassThru -Restart -WhatIf
# Remove the computer from the target group
# THIS SHOULD NOT OCCUR IF THE RENAME ABOVE FAILED!!!
Remove-ADGroupMember -Identity "CN=RenameComputer,CN=Users,DC=int,DC=example,DC=com" -Members $NewComputerName -WhatIf
}
TL;DR: This script finds a bunch of computers in a designated group, renames them, and then removes them from the group. I need help telling the script to NOT remove them from the group in the event that the rename fails (machine is offline, etc.) or throws some error.
Thoughts?
Thanks in advance!
There are a few ways you can check the result of a cmdlet in Powershell.
Run the cmdlet as an if statement condition
You can use the returned object of a cmdlet as an if statement condition, as long as it returns an object on success (which is why we need -PassThru for Rename-Computer, as normally it doesn't return an object). So to use your code as an example (and removing the -WhatIf):
# If Rename-Computer succeeds...
if( Rename-Computer -ComputerName $_.Name `
-NewName $NewComputerName -Force -PassThru -Restart ) {
# then remove the Computer from group
Remove-ADGroupMember -Identity "CN=RenameComputer,CN=Users,DC=int,DC=example,DC=com" `
-Members $NewComputerName
}
This works because objects are truthy in Powershell. Most defined objects and any number other than 0 (zero) are considered $True, but values like $null, "" (an empty string), and 0 (zero) are evaluated as $False. So in the case of your code, if it succeeds an object will be returned ($True), if an error occurs there will be no object returned ($False).
Explicitly check cmdlet result
Alternatively, you could run Rename-Computer first, then check its success:
Rename-Computer -ComputerName $_.Name -NewName $NewComputerName -Force
# Check success of last cmdlet
if( $? ) {
Remove-ADGroupMember -Identity "CN=RenameComputer,CN=Users,DC=int,DC=example,DC=com" `
-Members $NewComputerName
}
$? evaluates the success of the last cmdlet run (don't confuse this with $LASTEXITCODE, which is for checking the last run program result, not cmdlet result). If the cmdlet succeeded, it returns $True, if not, $False.
Catch Errors in a Try/Catch Block
A third way to do it would be to use a try/catch block, though you have to make sure that any error is terminating (will stop the script if uncaught) to catch an exception. You can use the -ErrorAction Stop paremeter for this:
try {
Rename-Computer -ComputerName $_.Name -NewName $NewComputerName -Force `
-PassThru -Restart -ErrorAction Stop
Remove-ADGroupMember -Identity "CN=RenameComputer,CN=Users,DC=int,DC=example,DC=com" `
-Members $NewComputerName
} catch {
# Do something on failure
Write-Warning "An error occurred: $($_.Exception.Message)"
}
If Rename-Computer throws an error within the try block, which it will on failure, the execution jumps to the catch block and will skip over Remove-ADGroupMember. If using a try/catch is your preferred method of error handling, you may consider setting $ErrorActionPreference = 'Stop' in your scripts, which sets the default -ErrorAction behavior to Stop, and you don't have to specify it on every cmdlet.
Additional Information
Here are some official docs about try/catch/finally and if statements in Powershell. I did not cover finally above but this is an optional part of try/catch statements:
About If: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_if?view=powershell-6
About Try/Catch/Finally: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_try_catch_finally?view=powershell-6
Here is also some advanced information about exceptions, errors, and handling them in Powershell: https://kevinmarquette.github.io/2017-04-10-Powershell-exceptions-everything-you-ever-wanted-to-know/

How do I output each processing item to file?

Get-AppXPackage -AllUsers | Foreach {Add-AppxPackage -DisableDevelopmentMode -Register "$($_.InstallLocation)\AppXManifest.xml"} | Out-File -path $env:temp\AppXPackage.log
This performs the task that I want it to perform. However, the log file is blank.
I would like for the log file to contain both the errors, and log the individual operations.
I'm not familiar with that particular cmdlet, you could probably try redirecting other output streams (so not stdout) to the log file, and it might work. at the very worst you can do something like this:
Foreach-Object {
try {
Add-AppxPackage ... -ErrorAction Stop
"$_ done" > success.log
} catch {
$_ > errors.log
}
}
The Add-AppxPackagedoesn't return anything, neither does it support -PassThru argument. However, you could push the $_ object to the pipe from the foreach block:
Get-AppXPackage -AllUsers |
Foreach {Add-AppxPackage -DisableDevelopmentMode -Register
"$($_.InstallLocation)\AppXManifest.xml"; $_ } |
Out-File -path $env:temp\AppXPackage.log
As the other 2 answers mentioned, that cmdlet doesn't seem to give any output. Which leaves you with either manual logging or redirection. I just wanted to expand a bit on the other two to give you a couple options.
For redirection, you can do something like this:
.\my_script *>> myfile.log #appen all streams (output and errors) to a file
.\my_script >> output.log 2>> errors.log #send output to one file and error stream to another
For normal logging, take a look at $error, which will contain all the errors in your session. You could start your script with $error.clear(), manually log your output you want since the cmdlet doesn't have output and then end with $error | out-file errors.log

Powershell Logging Errors

I have a simple script to delete a file, I know it could be more robust, but here it is
$LogFile = ".\deleteRegPol.log"
try
{
Remove-Item "c:\test\new text document.txt" -force
Add-Content $LogFile -Value "We ran the command"
}
catch [Exception]
{
Add-Content $LogFile -Value $_
}
finally
{
}
When the file I am trying to delete doesn't exist, I get an error on the command line but in my log file, it says the command ran. This is telling me that an exception was not thrown resulting in the flow going to the catch block. Why not?
PowerShell does not normally throw an exception when there is an error. Instead it writes a record to the error stream, which you can redirect. To force it to throw there are two options. One is to set the global error preference to stop:
$ErrorActionPreference = "Stop"
The other is to set the ErrorAction parameter to stop. This is supported for cmdlets that accept the so-called common parameters, which Remove-Item does:
Remove-Item "c:\test\new text document.txt" -force -EA "Stop"
To redirect the error stream you use the code 2:
Remove-Item "c:\test\new text document.txt" -Force 2>> $LogFile
That would append the error to the log file. The record isn't written to the error stream if your option is "Stop" however. It is simply included in the exception that is thrown.
Add the ErrorAction switch to your Remove-Item command:
Remove-Item "c:\test\new text document.txt" -force -ErrorAction Stop
There's quite a good treatment on error handling here:
http://blogs.msdn.com/b/kebab/archive/2013/06/09/an-introduction-to-error-handling-in-powershell.aspx