Send outlook email from powershell script - powershell

When I write the following script into the powershell command line one by one, it successfully sends the email, but when I run the script, it returns a bunch of errors. I'm guessing something syntactically needs to be changed in order to run it as a script? Any ideas?
Start-Process Outlook
$o = New-Object -com Outlook.Application
$mail = $o.CreateItem(0)
#2 = high importance email header
$mail.importance = 2
$mail.subject = “Auto Build Test“
$mail.body = “This is a test“
#for multiple email, use semi-colon ; to separate
$mail.To = “myemail#company.com"
$mail.Send()
# $o.Quit()

Param(
[parameter(Mandatory=$true)]
[alias("e")]
[string]$RecipientEmailAddress
)
if($RecipientEmailAddress -notmatch "\b[A-Za-z0-9._%+-]+#BLAHH.com")
{
Write-Output "the email address for the receipient of log reports is not a valid email address, hence will not send the report via email. They can still be accessed at " |Out-String ;
}else
{
$returnVal= New-Object PSObject ;
$returnVal |Add-Member -Name is_Success -MemberType NoteProperty -Value $null;
$returnVal |Add-Member -Name Explanation -MemberType NoteProperty -Value $null;
try{
$Attachments =Get-ChildItem -Path "C:\FOLDERWHEREYOURAATACHMENTS ARESTORED";
if($Attachments.count -eq 0)
{
$returnVal.Explanation="Error sending log report email to the user: $RecipientEmailAddress. Please check if the C:\FOLDERWHEREYOURAATACHMENTS is accessible and there are indeed log files present";
#Write-Output "Error sending log report email to the user: $RecipientEmailAddress" |Out-String ;
#Write-Output "Please check if the C:\FOLDERWHEREYOURAATACHMENTS is accessible and there are indeed log files present "|Out-String;
$returnVal.is_Success= $false;
return $returnVal;
}
$TestedAttachmentsList = new-Object System.Collections.ArrayList;
for($i=0;$i -lt $Attachments.count;$i++)
{
$TestedAttachmentsList.add($Attachments[$i].FullName);
}
Send-MailMessage -From "<FROM#BLAHH.COM>" -To "<$RecipientEmailAddress>" -SmtpServer "mail.BLAHH.com" -Attachments $TestedAttachmentsList -Subject "BLAHH SUBJECT" -Body "BLAHH BLAHH";
$returnVal.is_Success=$true;
$returnVal.Explanation="An email has been sent to the $RecipientEmailAddress containing the log of the setup and configuration."
return $returnVal ;
}Catch [System.Exception]
{
#Write-Output "Error sending log report email to the user: $RecipientEmailAddress" |Out-String ;
#Write-Output "Please check communication between your host machine and mail.BLAHH.com on port 25 is possible"|Out-String;
$returnVal.is_Success= $false;
$returnVal.Explanation="Error sending log report email to the user: $RecipientEmailAddress Please check communication between your host machine and mail.BLAHH.com on port 25 is possible";
return $returnVal ;
}
}

The syntax doesn't change between the command line and a script file. What changes is how fast the commands are executed. If you're typing them in then there is plenty of delay between each command. But if they run from a script they get presented to Outlook much more quickly.
A simple way to fix this is add Sleep 1 (or similar) before the command that fails. Without seeing your error output I would guess that you want to sleep after CreateItem and maybe before Send. But if you look at the error messages closely you'll see they identify which line of the script failed. Put Sleep before the first line that failed. Retry the script. If a new line fails, then put a delay before it as well. If the first line still fails you can try Sleep 2. You can also make the Sleep shorter. For 1/2 second: Sleep -milliseconds 500.
IF adding Sleeps fixes the problems - in other words the problem is a synchronization issue, there may be something in the Outlook object model you could use that wouldn't be as hackish as using Sleeps.
I wasn't able to repro this on my Outlook 2010 installation. However I did look up an alternate method for sending email from PS (below). Maybe this method will work.
$i=$o.Session.folders.item(2).folders.item("Outbox").items.add(0)
$i.to="me#whereiwork.com"
$i.Subject="a wittle testy"
$i.Body="some body"
$i.send()

Related

Powershell: Gathering tested items into a list to send in email

Problem Summary: I am attempting to write a powershell script to detect outbound open ports from a server, then emailing the name of the server and the array of ports detected as open.
Things I can do at the moment:
Scan and test the outgoing port list (1-1024 ports).
Send an email outlining the which machine is effected.
What I want to do:
Only send the email when any of the 1024 ports are listed as 'open'.
List the ports that were found to be open, ignoring ports that were closed.
Here is my code so far (shamelessly stolen from https://www.blackhillsinfosec.com/poking-holes-in-the-firewall-egress-testing-with-allports-exposed/):
1..1024 | % {$test= new-object system.Net.Sockets.TcpClient;
$wait = $test.beginConnect("allports.exposed",$_,$null,$null);
($wait.asyncwaithandle.waitone(250,$false));
if($test.Connected){echo "$_ open"}else{echo "$_ closed"}} | select-string " "
Send-MailMessage -From OutboundScanning#domain.com -To Myself#domain.com -SmtpServer mysmtp.domain.com -Subject "$env:computername site has exposed outbound ports" -Body "Please contact NOC and NetSec to correct site of $env:computername . Outgoing ports $openedarray are available for outbound connectivity."
I am going to provide a bit of edification, but on your next trip here, the rules are:
How do I ask a good question?
How to create a Minimal, Reproducible Example
Why is "Can someone help me?" not an actual question?
# Refactor and fixes to make this more readable
1..1024 |
ForEach {
<#
Unless you are using a legacy version of PowerShell, why are you using the .Net
namespace vs using the built-in PowerShell cmdlet ...
# Get specifics for a module, cmdlet, or function
(Get-Command -Name Test-NetConnection).Parameters
(Get-Command -Name Test-NetConnection).Parameters.Keys
Get-help -Name Test-NetConnection -Examples
<#
# Results
Test-NetConnection -InformationLevel "Detailed"
Test-NetConnection -Port 80 -InformationLevel "Detailed"
Test-NetConnection -ComputerName "www.contoso.com" -InformationLevel "Detailed"
Test-NetConnection -ComputerName www.contoso.com -DiagnoseRouting -InformationLevel Detailed
Test-NetConnection -ComputerName "www.contoso.com" -ConstrainInterface 5 -DiagnoseRouting -InformationLevel "Detailed"
Get-help -Name Test-NetConnection -Full
Get-help -Name Test-NetConnection -Online
#>
Your fix is to put the Send mail thing in the if statement.
Yet, you are saying 'if any', which means on the first open hit, you need to exit the loop.
$test = new-object system.Net.Sockets.TcpClient
$wait = $test.beginConnect("allports.exposed",$_,$null,$null)
($wait.asyncwaithandle.waitone(250,$false))
if($test.Connected)
{
"$PSItem open"
$SendMailMessageSplat = #{
From = "OutboundScanning#domain.com"
To = "Myself#domain.com"
SmtpServer = "mysmtp.domain.com"
Subject = "$env:computername site has exposed outbound ports"
Body = "Please contact NOC and NetSec to correct site of $env:computername.
Outgoing ports $openedarray are available for outbound connectivity."
}
Send-MailMessage #SendMailMessageSplat
Break
}
else{"$PSItem closed"}
}
You could refactor this using the normal PowerShell cmdlets, to this...
$SendMailMessageSplat = #{
From = "OutboundScanning#domain.com"
To = "Myself#domain.com"
SmtpServer = "mysmtp.domain.com"
Subject = "$env:computername site has exposed outbound ports"
Body = "Please contact NOC and NetSec to correct site of $env:computername.
Outgoing ports $openedarray are available for outbound connectivity."
}
1..1024 |
ForEach{
If (Test-NetConnection -ComputerName $env:COMPUTERNAME -Port $PSItem)
{
# Using Write-Host because of using colorized text, otherwise not really needed, since output to the screen is the PowerShell default.
Write-Host "Port $PSItem on the target host open. Sending email notification and exiting." -ForegroundColor Red
# Send-MailMessage #SendMailMessageSplat
Break
}
}
To do this as a collection ($openedarray), then you need to set it so, which you are not showing at all. This will also be very slow, just an FYI...
So, stuff like this...
$OpenPorts = #()
$SendMailMessageSplat = #{
From = "OutboundScanning#domain.com"
To = "Myself#domain.com"
SmtpServer = "mysmtp.domain.com"
Subject = "$env:computername site has exposed outbound ports"
Body = "Please contact NOC and NetSec to correct site of $env:computername.
Outgoing ports $OpenPorts are available for outbound connectivity."
}
1..1024 |
ForEach{
If (Test-NetConnection -ComputerName $env:COMPUTERNAME -Port $PSItem)
{
Write-Host "Port $PSItem on the target host open. Continnuing collection" -ForegroundColor Red
$OpenPorts += ,$PSItem
}
}
If ($OpenPorts -ge 1)
{
Write-Warning -Message "A total of $($OpenPorts.Count) ports discovered as open. Sending email notificaiton and exiting."
# Send-MailMessage #SendMailMessageSplat
}
Why would you want to send hundreds of ports in the body of an email?
It would be more prudent, to generate a txt/csv/excel report that you can just send. If you really want this in an email, then you need to generate this as a table using HTML body.

Emails not sending when calling multiple scripts

Good afternoon -
I have a "trigger file" that calls several scripts to run some morning reports. Each called script contains code that attaches a file to an email and sends (via ComObject). If I run each script individually then there all emails send correctly. However, when i run the "trigger file", only a couple of the emails send and I am not receiving any error messages. Any ideas what is happening? I purposely made the trigger file run the scripts concurrently to save time. But is that overloading Outlook?
EDIT: Updated code to include Try/Catch block. There are 8 scripts that run using this template. All 8 successfully complete the excel open/run/save. However, only some of the emails send. And even with this Try/Catch block, no error message is being sent.
#Establish script file locale
$FullPath = “//fullpath/”
$SavePath = “//savepath/”
#Open Excel file
& {
$Excel = New-Object -ComObject excel.application
$Excel.Visible=$False
$Workbook = $Excel.Workbooks.Open($FullPath)
#Run Macro
$app=$Excel.Application
$app.Run("Macro1")
#Save and close Excel
$Excel.Application.DisplayAlerts=$False
$Workbook.SaveAs($SavePath,51)
$Workbook.Close()
$Excel.Quit()
}
#Send email with attachment
Try
{
$EmailSettings=#{
SMTPServer = "smtp"
From = "me#email.com"
To =
#(
"you#email.com"
)
Subject = "Subject"
Attachments = $SavePath
BodyAsHtml = $true
Body =
"<body><p>
Attached is the thing.
</b></p></body>"
}
Send-MailMessage #EmailSettings
}
Catch
{
$Subject = 'ERROR: '+$EmailSettings.Subject
$ErrorMessage = $_.Exception.Message+' '+$_.Exception.ItemName
Send-MailMessage -From me#email.com -To me#email.com -Subject $Subject -SmtpServer smtp -Body $ErrorMessage
}

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 script for monitoring a list of files in parallel

We have about a half dozen servers that ftp a pair of text file with a status update every five minutes. We're trying to use a combination of Windows Task Scheduler and a Powershell script as the monitor and alert engine. The script is looking for files that have not been refreshed in the last 60 minutes and reading the files for any condition other than 'OK'. We'd like the scheduled task to run every five minutes.
When one of the while conditions are true for a file defined in $statusfiles (see script below) the script is getting hung up looping the same file while it waits for the condition to be false rather than processing the next file in the list and watching the problem files in the background. We want to know as soon as the script is run if there's a problem reported by any of the status files.
In my first version I used an If-ElseIf-Else approach. This gave me the immediate notification I was looking for but if I don't loop the alert conditions I don't know how to set a retry interval ($retryinterval) to give us a grace period to fix the underlying problem.
Is it possible to make the foreach method run in parallel against all the files defined in $statusfiles? Would this be system intensive? Or is there another approach I'm not seeing?
Thanks in advance for the help!
$erroractionpreference="SilentlyContinue"
$filepath = "\\Server\DirectoryA\DirectoryB\DirectoryC"
$statusfiles = "$filepath\opmessage21.txt","$filepath\pgmessage21.txt","$filepath\opmessage23.txt","$filepath\pgmessage23.txt","$filepath\opmessage24.txt","$filepath\pgmessage24.txt","$filepath\opmessage25.txt","$filepath\pgmessage25.txt","$filepath\opmessage26.txt","$filepath\pgmessage26.txt"
$agelimit = "60"
$retryinterval = "1800"
$to = "recipient1#email.com","recipient2#email.com","recipient3#email.com","recipient4#email.com"
$from = "alert#email.com"
$smtp = "smtp.server.com"
$port = "25"
function send-email
{
Send-MailMessage -Body "$body" -to $to -from $from -Subject "$subject" -smtp $smtp
}
foreach ($statusfile in $statusfiles) {
# Initialize the filestale and file error variables and strip the path and extension from $statusfile
$filestale = 0
$fileerror = 0
$messageid = $statusfile.split('\.')[-2]
# Get LastWriteTime of the file and alert the admin if LastWriteTime is older than X minutes
$lastupdated = (get-childitem $statusfile).LastWriteTime
while ($lastupdated -lt (get-date).AddMinutes(-$agelimit)) {
$filestale = 1
write-host "$messageid is older than $agelimit minutes. (Last updated: $lastupdated)"
$subject = "$messageid is older than $agelimit minutes"
$body = "Last updated: $lastupdated"
send-email
start-sleep -s $retryinterval
$lastupdated = (get-childitem $statusfile).LastWriteTime
}
# Alert the admin the file is no longer outdated
if ($filestale = 1) {
$filestale = 0
write-host "$messageid has been refreshed. (Last updated: $lastupdated)"
$subject = "$messageid has been refreshed"
$body = "Last updated: $lastupdated"
send-email
}
# Check the file for an error and alert the admin if the status is not OK
$messagecontents = Get-Content -Path "$statusfile" -Raw
while ($messagecontents -ne 'OK') {
$fileerror = 1
write-host "$messageid reported the following error: $messagecontents (Last updated: $lastupdated)"
$subject = "Error reported by $messageid"
$body = "$messagecontents (Last updated: $lastupdated)"
send-email
start-sleep -s $retryinterval
$messagecontents = Get-Content -Path "$statusfile" -Raw
$lastupdated = (get-childitem $statusfile).LastWriteTime
}
# Alert the admin the status is now OK
if ($fileerror = 1) {
$fileerror = 0
write-host "$messageid indicates status OK. (Last updated: $lastupdated)"
$subject = "$messageid indicates status OK"
$body = "Last updated: $lastupdated"
send-email
}
}

Best practice for emailing a body of appended strings?

I have a Powershell script that automates a process and emails the report of what happened.
Send-MailMessage -To $toAddress -From "no-reply#domain.org" -subject "Automation status" -body $bodystr -SmtpServer SERVER1 -EA Stop
So $bodystr is essentially an appended string throughout the script to report what happened and has multiple lines. Things like:
$bodystr = $bodystr + "Line found: 305`n"
$bodystr = $bodystr + "Moving line 305 to 574`n"
The Send-MailMessage command is at the bottom of the script outside any function. But most other code is in various different functions.
The issue is $bodystr does not seem accessible inside functions, and so the email is lacking a lot of information.
I believe I could use Set-Variable or passing arguments, but there are so many arguments it seems farther away from best practice to add a new argument for each function just to keep the string updated.
What's the best practice to handle this?
As a general rule, don't write data back to variables outside the scope of your function.
If you are compiling an email by gathering data from multiple sources, abstract it away in multiple functions that does one thing each and have them return a multiline string with the relevant output.
At the end of your script, collect the different message body parts and join them to a single string before sending.
In this example, we have a script that takes a path to a log file, defines a function to extract errors from a log file, and send an email with the errors in the body:
param(
[ValidateScript({Test-Path $_ -PathType Leaf })]
[string]$LogPath = 'C:\Path\To\File.log',
[string]$From = 'noreply#company.example',
[string]$To = #('ceo#company.example','finance#company.example'),
[string]$Subject = 'Super Important Weekly Report',
[string]$SmtpServer = $PSEmailServer,
[string]$Credential
)
# Define functions with a straight forward purpose
# e.g. Searching a logfile for errors
function Parse-Logfile {
param($LogPath)
[string[]]$LogErrors = #()
Get-Content $LogPath |ForEach-Object{
if($_ -contains $Error){
$LogErrors += $_
}
}
# Create and return a custom object has the error details as properties
New-Object psobject -Property #{
ErrorCount = $LogErrors.Count
Errors = $LogErrors
}
}
# Create a email template that's easy to maintain
# You could store this in a file and add a $TemplateFile parameter to the script ;-)
$EmailTemplate = #'
Hi there!
Found {0} errors in log file: {1}
{2}
Regards
Zeno
'#
# Use your function(s) to create and gather the details you need
$ErrorReport = Parse-Logfile -LogPath $LogPath
# If necessary, concatenate strings with -join
$ErrorString = $ErrorReport.Errors -join "`n"
# Use the format operator to the final Body string
$Body = $EmailTemplate -f $ErrorReport.ErrorCount, $LogPath, $ErrorString
# Set up a splatting table (Get-Help about_Splatting)
$MailParams = #{
To = $To
From = $From
Subject = $Subject
Body = $Body
SmtpServer = $SmtpServer
}
if($PSBoundParameters.ContainsKey('Credential')){
$MailParams['Credential'] = $Credential
}
# Send mail
Send-MailMessage #MailParams