Catch error from Send-MailMessage cmdlet when called from batch file - powershell

I have a batch file that uses PowerShell's Send-MailMessage cmdlet to send emails out with status updates:
powershell -command " & {Send-MailMessage -SMTPServer smtp.somewhere.com -From 'Sender <sender#email.com>' -To 'Recipient <recipient#email.com>' -Subject 'Subject' -Body 'Body' -Attachments 'D:\Logs\Log.log';}"
if %ERRORLEVEL% neq 0 (
echo ERROR - Something went wrong sending the email
goto Error
)
If the Send-MailMessage command fails (e.g. if attachment file not found, or other error), the %ERRORLEVEL% is not raised above 0.
Other answers suggest adding explicit exit codes into a PowerShell script to return a code, but I can't find anything about how to capture an error if using a built in PS cmdlet like Send-MailMessage. Can it be done, or do I need to wrap Send-MailMessage in another PS script?

Wrap the whole command in a try catch block and throw an explicit error. This will return a non-zero exit code.
powershell -command "Try {Send-MailMessage ... -ErrorAction Stop;} Catch { Exit 1 }"
It's a little harder to read in a one line but:
powershell -command "Try {Send-MailMessage -SMTPServer smtp.somewhere.com -From 'Sender <sender#email.com>' -To 'Recipient <recipient#email.com>' -Subject 'Subject' -Body 'Body' -Attachments 'D:\Logs\Log.log' -ErrorAction Stop;} Catch { Exit 1 }"
if %ERRORLEVEL% neq 0 (
echo ERROR - Something went wrong sending the email
goto Error
)

Related

Sending E-mail when ms access.exe hangs powershell

I would like to ask for help if there is a way to send an e-mail notification when a scheduled ms access macro in task scheduler is in "Running" state for more than 20 minutes?
First thing that comes to mind is writing a second scheduled tasks to poll the runtime of the MS Access macro task.
If the task is over 20 minutes send out an email.
Another option might be to start a background job (Start-Job) in the MS Access code that will send the email after 20 minutes. And in your code, if the MS Access job succeeds you can cancel (Stop-Job) the email background task.
(There are many ways to skin a cat so just finding the best solution for the problem really)
Finally got it using powershell.
$ScheduledTaskName = "full path of your scheduled task e.g Folder1\Folder2\taskname"
$Result = (schtasks /query /FO LIST /V /TN $ScheduledTaskName | findstr "Result")
$Result = $Result.substring(12)
$Code = $Result.trim()
If ($Code -gt 0) {
$From = "MacroFails#DoNotReply.com"
$To = "Admin#Domain.com"
$Subject = "Scheduled task 'AppendCreatedSO' failed on SERVER"
$Body = "Error code: $Code"
$SMTPServer = "your SMTP Server"
$SMTPPort = "SMTP erver port"
Send-MailMessage -From $From -to $To -Subject $Subject -Body $Body -BodyAsHtml -SmtpServer $SMTPServer -Port $SMTPPort #-UseSsl -Credential (Get-Credential) #-Attachments $Attachment
}

Send-MailMessage fails on long unc file name with $ character

Testing this for another script Im working on, but having trouble with a new group of files. See below. $Attach2 causes the error while $Attachment1 works fine
$Attachment1 = "c:\temp\I went to the beach-and now too.txt"
$Attach2 = '\\qa-west\e$\orders\15557__45747457-Re_[EXTERNAL]SomeBoxShipmentTestName-JoeSmithers-FileWest-232264_42211_3674745752.msg'
$smtpServer = "mail.somewhare.com"
try{
Send-MailMessage -From 'nobody#somwhare.com' -To 'testdev#somewhare.com' -Subject 'test sub' -Body 'this is body' -SmtpServer $smtpServer -Attachments $Attachment1
}
catch {
# log the error
$ErrorMessage = $PSItem.Exception.Message
#-- test
Write-Host $ErrorMessage
}
Send-MailMessage fails with exception:
Exception:System.IO.FileNotFoundException: Unable to find the
specified file.\r\n at
System.Management.Automation.MshCommandRuntime.ThrowTerminatingError(ErrorRecorderrorRecord)
ErrorDetails:Cannot perform operation because the wildcard path did not resolve to a file FileName $null
I don't think the problem is the $- I think it's the square brackets. Square brackets are wildcards in PowerShell, and Send-MailMessage doesn't support wildcards in -Attachments
Add a backtick (`) in front of the two square brackets in your filename. There's some more details about this issue available here

Power shell send email in loop and write output to file

Send-MailMessage -From "test#xxx.com" -To "aaa#xxx.com","bbb#xxx.com","ccc#xxx.com","ddd#xxx.com" -SmtpServer "xx.xx.xx.xx" -Subject "Test email subject" -Body "Test email body"
I want to write a script using above command in loop say 100 times and run every 2 seconds and write an output to a log file which later can be used for analysis.
Issue: My severer gives intermittent issue while sending emails. I want to see logs to see how many emails go through out of 100 and how many failed and with what error message.
Running multiple times would require some sort of loop. A for loop seems most appropriate for each loop of 100, and a while loop seems appropriate for looping over each set.
Since you are running commands very quickly you will want some sort of parallelism. Jobs, Runspaces, and workflows are the three most common forms. Jobs are the easiest to work with but the least efficient.
Then the Start-Sleep command can be used for the delay between trials.
There are many ways to write out to a file, Out-File -Append is one way. Try\catch can be used to catch the errors and output those to the log as well.
$Iterations = 100
$Delay = 2
$StopTime = (Get-Date).AddMinutes(5)
$Command = {Send-MailMessage -From "test#xxx.com" -To "aaa#xxx.com","bbb#xxx.com","ccc#xxx.com","ddd#xxx.com" -SmtpServer "xx.xx.xx.xx" -Subject "Test email subject" -Body "Test email body"}
while ((Get-Date -le $StopTime)) {
try {
Remove-Job -State Completed
for ($i = 0; $i -lt $Iterations; $i++) {
Start-Job -ScriptBlock $command
}
"Jobs created at (Get-Date)" | Out-File "C:\example\log.txt" -Append
Start-Sleep $delay
}
catch {
$error[0] | Out-File "C:\example\log.txt" -Append
}
}

Powershell command returns null on success

I am writing small utility scripts in Powershell. While trying to follow the principle of doing one job extremely well per script, I am running into an issue with one script that returns a null value on success. When I try to run the command, and pipe it through a command such as Send-MailMessage, I get an error when it should be successful. I am trying to get Send-MailMessage to only fire if there is output in the body (otherwise, there would be many emails coming in).
Currently, I'm trying to run this from the Powershell command line:
Send-MailMessage -From "powershell#example.com" -To "tech#example.com" -Subject "Disk usage on $($env:COMPUTERNAME)" -SmtpServer "192.168.0.1" -Body (.\diskUsage.ps1 | Out-String) -ErrorAction Continue
The error I'm receiving:
Send-MailMessage : Cannot validate argument on parameter 'Body'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
At line:1 char:156
+ ... SmtpServer "192.168.0.1" -Body (.\diskUsage.ps1 | Out-String) -ErrorA ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Send-MailMessage], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.SendMailMessag
My goal is to run the command remotely to many computers, and only get email notifications when the disk usage is above a threshold. My script currently returns nothing if the disk space check is OK, and this is my expected behaviour. I need Send-MailMessage, on the command itself, to simply not run if there is nothing to put in the body, without an error appearing in the console that the body is empty.
If specified, the body parameter is going to be verified for not being null or empty. Here is the body part from the open source version of PowerShell:
/// <summary>
/// Specifies the body (content) of the message
/// </summary>
[Parameter(Position = 2)]
[ValidateNotNullOrEmpty]
public String Body
{
get { return _body; }
set
{
_body = value;
}
}
private String _body;
Notice that if no Body is assigned the default value is going to be used which is not defined but rather an implementation detail and in this example null (Send-MailMessage). Therefore not specifying the Body is what you would like to todo.
To ensure clean code you can use PowerShells splatting features or alternativly use simple branching using if statements.
Simple If
$diskUsage = (.\diskUsage.ps1 | Out-String)
If ($diskUsage){
# Disk usage was set
Send-MailMessage -From "powershell#example.com" -To "tech#example.com" -Subject "Disk usage on $($env:COMPUTERNAME)" -SmtpServer "192.168.0.1" -Body $diskUsage -ErrorAction Continue
} Else {
# Disk usage was not set notice that -body is omitted which is different from setting it to null or empty
Send-MailMessage -From "powershell#example.com" -To "tech#example.com" -Subject "Disk usage on $($env:COMPUTERNAME)" -SmtpServer "192.168.0.1" -ErrorAction Continue
}
First, emails are usuallly UTF8 to set the encoding of the body to UTF8 by using -Encoding UTF8
If you are trying to capture the output of diskUsage.ps1 it probably won't return a string like you think.
You can test it by running:
$output = & C:\path\to\diskUsage.ps1 | Out-String
if([String]::IsNullOrEmpty($output)) { echo 'no string is being returned' } else { echo 'String returned' }
If it says no string is being returned, then you should rethink this command into a script.
e.g. put the diskUsage.ps1 and this Send-MailMessage command into one script.

IF statement + send-mailmessage confused

Quick question;
I want to make a log of bad stuff happening to the servers, but that's not the question.
The log should be deleted upon send, and if there is no log at the time the script is being run it should send a different message, without an attachment.
Here's the script so far:
<#
Set-ExecutionPolicy unrestricted -Force
iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))
cinst BetterCredentials
#>
Import-Module BetterCredentials
$Mailcred = Get-Credential -user user#gmail.com -pass 12345password
Try{
$File = C:/path/errorfile.log
}
Catch [System.Management.Automation.PSArgumentException]
{
"invalid object"
}
Catch [system.exception]
{
"caught a system exception"
}
Finally
{
If ($File -eq ){
then (send-mailmessage -ErrorAction SilentlyContinue -from "Server <Server#company.com>" -to "IT Dept. <Itdept#company.com>" -subject "Log of the day." -body "Good Morning,
Fortuneatly, nothing bad to report today!" -priority High -dno onSuccess, onFailure -smtpServer smtp.company.com -credential ($Mailcred) -usessl)
else (send-mailmessage -ErrorAction SilentlyContinue -from "Servers <Server#company.com>" -to "IT Dept. <ITDept#company.com>" -subject "Log of the day." -body "Good Morning,
Here is your daily report." -Attachments $File -priority High -dno onSuccess, onFailure -smtpServer smtp.company.com -credential ($Mailcred) -usessl)
}
}
What AM I doing wrong here?
Some parts of your code do not make any sense. Let's review those.
$File = C:/path/errorfile.log
...
If ($File -eq ){
The first part is about assignment. In order to find out wether errorfile.log exists, use the Test-Path cmdlet like so,
$File = test-path 'C:/path/errorfile.log'
Always use quotes around file name. If you got spaces within path, unquoted path doesn't work.
The second part is the actual test.
If ($File){
... # file was on disk, send alert and delete it
} else {
... # no file found, send ok
}