(PowerShell) Log Exceptions and Continue (Active Directory Module) - powershell

I'm using New-ADUser and Add-ADGroupMember
If the user already exists or is already in the group then the functions throw exceptions (which are expected and not a problem).
How do I log the exceptions to file and keep going?
Redirection is not working - the exceptions always go to the
console.
-ErrorAction is not working - exceptions still go to the console
Try / Catch works, but execution stops and the rest of the commands don't run
I can do a Try / Catch for every single statment, but that seems
ridiculous

You can combine -ErrorAction SilentlyContinue with -ErrorVariable:
$e = $null
New-ADUser iExist -ErrorAction SilentlyContinue -ErrorVariable e
$e # contains the error
You can also use the built in $Error variable, which is a circular buffer holding all the errors.
$ErrorPreference = SilentlyContinue # I don't like this personally
New-ADUser iExist
Add-ADGroupMember iExist iForgotTheParameters
$Error[0] # The Add-ADGroupMember error
$Error[1] # The New-ADUser error
So you could set your $ErrorPreference, do a bunch of commands, and at the end of it all, do something like $Error | Out-File -Path errors.txt.
Have a look at PowerShell Error Handling and Why You Should Care for more ideas.

The simplest way to accomplish this is probably using the trap construct:
function Test-Trap{
trap {
$_ | Out-String | Out-File C:\path\to\errors.txt -Append
}
Get-ADUser -NoSuchParam "argument"
Write-Host "Show must go on"
nonexistingcommand
Write-Host "Still executing"
}
When you call Test-Trap, you'll see that after the error has been written to the console, the trap is executed, and the rest of the execution flow is resumed:
And the error record output as it would normally appear on screen (courtesy of Out-String) has been saved to the file:
You could add cool features like timestamps and stack traces to your trap:
function Test-Trap{
trap {
$LogPath = "C:\path\to\errors.txt"
$ErrorCount = $ErrorCount + 1
$("[Error {0} trapped {1}]:" -f $ErrorCount,(Get-Date -Format "dd/MM/yyyy HH:mm:ss.fff")) | Out-File $LogPath -Append
$_ | Out-String | Out-File $LogPath -Append
if(($st = $_.Exception.Stacktrace)){ $st |Out-File $LogPath -Append }
$("[Error {0} logged]" -f $ErrorCount)| Out-File $LogPath -Append
}
Provoke-Error -NoSuchParam muhahaha
}

Related

Is there a way to Expand-Archive without overwriting files?

Is there a way in PowerShell to use Expand-Archive so that files are written where they don't exist, but are not overwritten when they do exist? I can achieve this with -ErrorAction SilentlyContinue, but that ignores things that might be actual errors.
To silence only "file already exists" error messages of Expand-Archive, you can redirect the error stream to the success stream and process error records using ForEach-Object:
Expand-Archive -Path Test.zip -DestinationPath . -EA Continue 2>&1 | ForEach-Object {
if( $_ -is [System.Management.Automation.ErrorRecord] ) {
if( $_.FullyQualifiedErrorId -split ',' -notcontains 'ExpandArchiveFileExists' ) {
Write-Error $_ # output error that is not "file exists"
}
}
else {
$_ # pass success stream through
}
}
-EA Continue (-ErrorAction) overrides the preference variable $ErrorActionPreference to make sure errors are not turned into exceptions (in which case the first error would interrupt the extraction).
2>&1 redirects (merges) the error stream (#2) to the success stream (#1), so both can be processed using ForEach-Object.
$_ -is [System.Management.Automation.ErrorRecord] tests if the current pipeline element is an error record.
When this is the case, we test what kind of error we have, by checking the FullyQualifiedErrorId property of the ErrorRecord (the exception type System.IO.IOException would be too general to test for)
Otherwise it is a message from the success stream, which will be simply passed through.
In case you are wondering how I came up with that FullyQualifiedErrorId thing, I just run Expand-Archive without redirection and called Get-Error afterwards. This outputs all information of the last error record, so I could look up the information to detect the error condition.
An alternative solution, similar to the one suggested by Abraham Zinala, is to unconditionally silence all errors and use -ErrorVariable to collect the errors and shown the relevant ones after the call to Expand-Archive has returned:
$oldErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = 'SilentlyContinue'
$archiveErrors = $null
Expand-Archive -Path Test.zip -DestinationPath . -ErrorVariable archiveErrors
$ErrorActionPreference = $oldErrorActionPreference
$archiveErrors | Sort-Object { $_ | Out-String } -Unique | ForEach-Object {
if( $_ -is [System.Management.Automation.ErrorRecord] ) {
if( $_.FullyQualifiedErrorId -split ',' -notcontains 'ExpandArchiveFileExists' ) {
$_ # output error that is not "file exists"
}
}
}
The errors of Expand-Archive cannot be completely silenced through the -ErrorAction parameter, because some errors (like input file doesn't exist) are detected as part of parameter validation. To really silence all errors, the $ErrorActionPreference variable must be used.
It is important to set the error variable to $null before calling Expand-Archive because the command doesn't reset the variable, when there is no error.
The name of the variable passed to -ErrorVariable must be specified without $.
The Sort-Object -Unique command makes sure we don't show duplicate errors.

Powershell. Write event logs

I have a script which moves some files from a folder to the temp folder, archives them and cleans the temp folder at the end.
I want my script to also write information about it in the win-event log.
Here is my script:
Get-ChildItem C:\Users\Administrator\Desktop\test1\ | Where-Object {$_.LastWriteTime -lt "09/24/2018 09:00 PM"} | Move-Item -Destination C:\Users\Administrator\Desktop\data\
Compress-Archive -path C:\Users\Administrator\Desktop\data\ -CompressionLevel Optimal -DestinationPath C:\Users\Administrator\Desktop\data1\test.zip
Remove-Item C:\Users\Administrator\Desktop\data\*
I want to add code which will write an event for any error into the win-event log.
Per the comments, you can use Write-EventLog to write to the Windows Event Logs. If you want to write any errors that occur during those commands, then you probably want to use a Try..Catch to catch any errors and handle them:
Try {
$PrevEAP = $ErrorActionPreference
$ErrorActionPreference = 'Stop'
Get-ChildItem C:\Users\Administrator\Desktop\test1\ | Where-Object {$_.LastWriteTime -lt "09/24/2018 09:00 PM"} | Move-Item -Destination C:\Users\Administrator\Desktop\data\
Compress-Archive -path C:\Users\Administrator\Desktop\data\ -CompressionLevel Optimal -DestinationPath C:\Users\Administrator\Desktop\data1\test.zip
Remove-Item C:\Users\Administrator\Desktop\data\*
Catch {
Write-Error $_
$ErrorEvent = #{
LogName = 'Application'
Source = 'YourScript'
EventID = 123
EntryType = 'Information'
Message = $_
}
Write-EventLog #ErrorEvent
}
Finally {
$ErrorActionPreference = $PrevEAP
}
In order for an exception (error) to trigger a Try..Catch the exception needs to be terminating (vs non-terminating). You can force cmdlets to do terminating errors by setting the cmdlets -ErrorAction to Stop, or you can do this globally via the $ErrorActionPreference variable.
In the catch block, the error is held in the special variable: $_. So we can use Write-Error to still write it out to the console (if you want to) and then we're using Write-EventLog to write it into the Event Log.
Customise LogName, Source, EventID, Information etc. as per your needs. Note LogName needs to be one of the existing Logs and Entry Type needs to be one of the valid entry types (Information, Warning, Error).

Powershell Try catch write errors to an external txt file

I have a PowerShell script to disable email forwarding in Office 365 using a CSV file. I want to catch all errors to an external Txt file.
Here's my code:
$Groups |
ForEach-Object {
$PrimarySmtpAddress = $_.PrimarySmtpAddress
try {
# disable Mail forwarding
Set-Mailbox -Identity $PrimarySmtpAddress -ForwardingSmtpAddress $Null
}
Catch {
$PrimarySmtpAddress | Out-File $logfile -Append
}
}
but it doesn't catch errors.
Is there any instruction to catch errors to an external file?
Any solution will be helpful.
From Jeff Zeitlin's and TheMadTechnician's comments, changes noted in inline comments:
$Groups | ForEach-Object {
$PrimarySmtpAddress = $_.PrimarySmtpAddress
Try {
# disable Mail forwarding
#Added ErrorAction Stop to cause errors to be terminating
Set-Mailbox -Identity $PrimarySmtpAddress -ForwardingSmtpAddress $Null -ErrorAction Stop
}
Catch {
#Removed Write-Host as Write-Host writes to the host, not down the pipeline, Write-Output would also work
"$PrimarySmtpAddress" | Out-File $logfile -Append
}
}
Try this:
$ErrorMessage = $_.Exception.Message
#or $ErrorMessage= $Error[0].Exception.Message
if($ErrorMessage -ne $null) {
... Your custom code
}
Exchange Online (a.k.a O365) does not honor the thrown error for your catch statement to work. We have had to set
$global:ErrorActionPreference=Stop
to get the error object to return
Note: We implemented this using the following at beginning of function
$saved=$global:ErrorActionPreference
$global:ErrorActionPreference=Stop
and reverted the setting at the end of the function using
$global:ErrorActionPreference=$saved

Remote Powershell to retrieve specific registry value from lots of servers

I have the following..
$output = #()
$servers =Get-Content "C:\Windows\System32\List3.txt"
foreach ($server in $servers)
{
trap [Exception] {continue}
Import-Module PSRemoteRegistry
$key="SOFTWARE\Microsoft\'Microsoft Antimalware'\'Signature Updates'"
$regkey=Get-RegBinary -ComputerName $server -Key $Key -Value SignatuesLastUpdated
#$regkey=(Get-Item HKLM:\SOFTWARE\Microsoft\'Microsoft Antimalware'\'Signature Updates').getValue('SignaturesLastUpdated')
#$regkey=[datetime]::ParseExact("01/02/03", "dd/MM/yy", $null) | Export-csv -path c:\temp\avinfo.csv -append
#$regkey
}
$output | Select $server , $Regkey | Export-Csv c:\temp\avinfo.csv -NoTypeInformation
I think it's pretty close but doesn't work as needed - can anyone tell me what I am doing wrong here - been reading a lot and managed to get this far, just need the help to finalise.
Thanks
Ok... so there is alot that needed to be changed to get this to work. I will update the answer frequently after this is posted.
$servers = Get-Content "C:\Windows\System32\List3.txt"
$key="SOFTWARE\Microsoft\Microsoft Antimalware\Signature Updates"
$servers | ForEach-Object{
$server = $_
Try{
Get-RegBinary -ComputerName $server -Key $Key -Value SignatuesLastUpdated -ErrorAction Stop
} Catch [exception]{
[pscustomobject]#{
ComputerName = $server
Data = "Unable to retrieve data"
}
}
} | Select ComputerName,#{Label=$value;Expression={If(!($_.Data -is [string])){[System.Text.Encoding]::Ascii.GetBytes($_.data)}Else{$_.Data}}} | Export-Csv c:\temp\avinfo.csv -NoTypeInformation
What the above code will do is more in line with your intentions. Take the list and for each item get the key data from that server. If there is an issue getting that data then we output a custom object stating that so we can tell in the output if there was an issue. The part that is up in the air is how you want to export the binary data to file. As it stands it should create a space delimited string of the bytes.
The issues that you did have that should be highlighted are
No need to import the module for every server. Moved that call out of the loop
You have declared the variable $output but do not populate it during your loop process. This is important for the foreach construct. You were, in the end, sending and empty array to you csv. My answer does not need it as it just uses standard output.
As #Meatspace pointed out you had a typo here: SignatuesLastUpdated
Get-RegBinary does not by default create terminating errors which are needed by try/catch blocks. Added -ErrorAction Stop. Don't think your code trap [Exception] {continue} would have caught anything.
The single quotes you have in your $key might have prevented the path from being parsed. You were trying to escape spaces and just need to enclose the whole string in a set of quotes to achieve that.
While Select can use variables they are there, in a basic form, to select property names. In short what you had was wrong.

Pipe all Write-Output to the same Out-File in PowerShell

As the title suggests, how do you make it so all of the Write-Outputs - no matter where they appear - automatically append to your defined log file? That way the script will be nicer to read and it removes a tiny bit of work!
Little example below, id like to see none of the "| Out-File" if possible, yet have them still output to that file!
$Author = 'Max'
$Time = Get-Date -Format "HH:mm:ss.fff"
$Title = "Illegal Software Removal"
$LogName = "Illegal_Remove_$($env:COMPUTERNAME).log"
$Log = "C:\Windows\Logs\Software" + "\" + $LogName
$RemoteLog = "\\Server\Adobe Illegal Software Removal"
Set-PSBreakpoint -Variable Time -Mode Read -Action { $global:Time = Get-Date -format "HH:mm:ss.fff" } | Out-Null
If((Test-Path $Log) -eq $False){ New-Item $Log -ItemType "File" -Force | Out-Null }
Else { $Null }
"[$Time][Startup] $Title : Created by $Author" | Out-File $Log -Append
"[$Time][Startup] Configuring initial variables required before run..." | Out-File $Log -Append
EDIT: This needs to work on PS v2.0, I don't want the output to appear on screen at all only in the log. So I have the same functionality, but the script would look like so...
"[$Time][Startup] $Title : Created by $Author"
"[$Time][Startup] Configuring initial variables required before run..."
You have two options, one is to do the redirection at the point the script is invoked e.g.:
PowerShell.exe -Command "& {c:\myscript.ps1}" > c:\myscript.log
Or you can use the Start-Transcript command to record everything (except exe output) the shell sees. After the script is done call Stop-Transcript.