What I’m doing now is simply refreshing a spreadsheet and emailing it out as an attachment. I would like to get a certain range in the spreadsheet and convert it into a jpg and then embed that into the body of the email. Hopefully this can also be done without actually having to open the spreadsheet. Below is just the code I’m using to send the email with the attachment. I got the bulk of this code online. I’m just starting out the PowerShell. I’m in the process of converting my auto generated Excel reports that use VBA in the Workbook_Open module to get the job done. I was able to find VBA code showing me how to get the pic into the email body but would LOVE to do it in PowerShell.
# Check if Outlook is running
if (!(Get-Process "OUTLOOK" -ErrorAction SilentlyContinue)) {
# Start Outlook if it's not running
Start-Process "OUTLOOK"
# Flag to track whether Outlook was open before running the script
$outlookWasRunning = $false
} else {
# Outlook was already running
$outlookWasRunning = $true
}
# Open the Excel file
$excel = New-Object -ComObject Excel.Application
$workbook = $excel.Workbooks.Open("C:\ScheduledReports\DSR\DSRBaseFile\DSRBaseFile.xlsx")
# Refresh the data in the workbook
$workbook.RefreshAll()
# Add a wait timer to allow the connection refresh to complete
Start-Sleep -Seconds 30
# Save the workbook to DailyUpdateFile location
$newFileName = "DSR.xlsx"
$ReportName = "DSR"
if (Test-Path "C:\ScheduledReports\DSR\DSRDailyUpdateFile\$newFileName") {
# Overwrite the existing file
[System.IO.File]::Delete("C:\ScheduledReports\DSR\DSRDailyUpdateFile\$newFileName")
}
# Remove the queries as they are not needed in the new file
foreach ($query in $workbook.Queries) {
$query.Delete()
}
# Save the workbook as a new file
$excel.DisplayAlerts = $false
$workbook.SaveAs("C:\ScheduledReports\DSR\DSRDailyUpdateFile\$newFileName")
# Close the workbook and Excel application
$workbook.Close()
$excel.Quit()
# Create and send an email
$ol = New-Object -ComObject Outlook.Application
$mail = $ol.CreateItem(0)
$mail.To = "someone@somewhere.com"
$mail.CC = ""
$mail.Subject = "DSR"
$mail.Body = "See attached $ReportName. Please do not reply to this email. For questions or concerns reach out to admin@company.com"
# Attach the file to the email if it exists
if (Test-Path "C:\ScheduledReports\DSR\DSRDailyUpdateFile\$newFileName") {
$mail.Attachments.Add("C:\ScheduledReports\DSR\DSRDailyUpdateFile\$newFileName")
}
$mail.Send()
# Add a wait timer to keep Outlook open long enough to send the email
Start-Sleep -Seconds 15
$excel.Quit()
# Close Outlook if it wasn't running before the script
if (!$outlookWasRunning) {
$ol.Quit()
}
With the wonderful ImportExcel module you can simplify the image creation. Unfortunately, a bug currently limits the Convert-ExcelRangeToImage cmdlet to PowerShell 5.1.
Thank you Matt! You got me this far: I am able to “attach” the picture to the email, but I can’t figure out how to “embed” the picture in the body of the email. So the script is just putting both the Excel file attachment AND the picture attachment in the email. I read a little about Mailozaurr but as a noob, I can’t figure out how to use it (or if it is entirely necessary). I was able to get it installed however! Can you fix up what I have below to get the image embedded instead of attached? Again, MANY THANKS!
# Check if Outlook is running
if (!(Get-Process "OUTLOOK" -ErrorAction SilentlyContinue)) {
# Start Outlook if it's not running
Start-Process "OUTLOOK"
# Flag to track whether Outlook was open before running the script
$outlookWasRunning = $false
} else {
# Outlook was already running
$outlookWasRunning = $true
}
# Open the Excel file
$excel = New-Object -ComObject Excel.Application
$workbook = $excel.Workbooks.Open("C:\ScheduledReports\DSRtestpic\DSRtestpicBaseFile\DSRtestpicBaseFile.xlsx")
# Refresh the data in the workbook
$workbook.RefreshAll()
# Add a wait timer to allow the connection refresh to complete
Start-Sleep -Seconds 30
$newFileName = "DSR.xlsx"
$ReportName = "DSR"
# Save the workbook with the current date
if (Test-Path "C:\ScheduledReports\DSRtestpic\DSRtestpicDailyUpdateFile\$newFileName") {
# Overwrite the existing file
[System.IO.File]::Delete("C:\ScheduledReports\DSRtestpic\DSRtestpicDailyUpdateFile\$newFileName")
}
# Remove the queries as they are not needed in the new file
foreach ($query in $workbook.Queries) {
$query.Delete()
}
# Save the workbook as a new file
$excel.DisplayAlerts = $false
$workbook.SaveAs("C:\ScheduledReports\DSRtestpic\DSRtestpicDailyUpdateFile\$newFileName")
#pic routine
$ConvertParam = @{
Path = 'C:\ScheduledReports\DSRtestpic\DSRtestpicBaseFile\DSRtestpicBaseFile.xlsx'
WorksheetName = 'Sheet3'
Range = 'A1:E7'
Destination = 'C:\ScheduledReports\DSRtestpic\picture\DSR.jpg'
}
Convert-ExcelRangeToImage @ConvertParam
# Close the workbook and Excel application
$workbook.Close()
$excel.Quit()
# Create and send an email
$image = 'C:\ScheduledReports\DSRtestpic\picture\DSR.jpg'
$ol = New-Object -ComObject Outlook.Application
$mail = $ol.CreateItem(0)
$mail.To = ""
$mail.CC = "someone@mycompany.com"
$mail.Subject = "DSR"
$mail.Body = "See attached $ReportName. For quick screenshot, please open DSR.jpg. `nPlease do not reply to this email. For questions or concerns reach out to dhausner@marcalpaper.com"
$mail.IsBodyHTML = $true
# Attach the file to the email if it exists
if (Test-Path "C:\ScheduledReports\DSRtestpic\DSRtestpicDailyUpdateFile\$newFileName") {
$mail.Attachments.Add("C:\ScheduledReports\DSRtestpic\DSRtestpicDailyUpdateFile\$newFileName")
$mail.Attachments.Add("C:\ScheduledReports\DSRtestpic\picture\DSR.jpg")
}
$mail.Send()
# Add a wait timer to keep Outlook open long enough to send the email
Start-Sleep -Seconds 15
$excel.Quit()
# Close Outlook if it wasn't running before the script
if (!$outlookWasRunning) {
$ol.Quit()
}
The attachment is referenced using the CID (Content-ID) value in the HTML. You just need to modify the body of the message to include the attachment name:
# Create and send an email
$messageBody = @"
<html>
<head></head>
<body>
<p>See attached $ReportName.</p>
<p>For a quick screenshot, please see below.</p>
<p>Please do not reply to this email.<br>
For questions or concerns reach out to me@mycompany.com</p>
<img id="1" src="cid:DSR.jpg">
</body>
</html>
"@
$ol = New-Object -ComObject Outlook.Application
$mail = $ol.CreateItem(0)
$mail.To = ""
$mail.CC = "someone@mycompany.com"
$mail.Subject = "DSR"
$mail.HTMLBody = $messageBody
# Attach the file to the email if it exists
if (Test-Path "C:\ScheduledReports\DSRtestpic\DSRtestpicDailyUpdateFile\$newFileName") {
$mail.Attachments.Add("C:\ScheduledReports\DSRtestpic\DSRtestpicDailyUpdateFile\$newFileName")
$mail.Attachments.Add("C:\ScheduledReports\DSRtestpic\picture\DSR.jpg")
}
$mail.Send()
Well, after having some time to dig in, I found one issue with the script below. It is NOT overwriting the jpg file located in C:\ScheduledReports\DSR\picture\DSR.jpg
Also, I commented out a bunch of code because I don’t think it’s needed. I’m also not sure what other code is not needed (especially when it comes to sending the email out).
I also had to comment out
###if (!$outlookWasRunning) {
### $ol.Quit()
###}
because it was closing Outlook. In all the other files I send out (without any picture stuff, just a reguler file attachment), Outlook stays open (which is what I want).
Again, any help getting this all cleaned up is gratly appreciated!
# Check if Outlook is running
if (!(Get-Process "OUTLOOK" -ErrorAction SilentlyContinue)) {
# Start Outlook if it's not running
Start-Process "OUTLOOK"
# Flag to track whether Outlook was open before running the script
$outlookWasRunning = $false
} else {
# Outlook was already running
$outlookWasRunning = $true
}
# Open the Excel file
$excel = New-Object -ComObject Excel.Application
$workbook = $excel.Workbooks.Open("C:\ScheduledReports\DSR\DSRBaseFile\DSRBaseFile.xlsx")
# Refresh the data in the workbook
$workbook.RefreshAll()
# Add a wait timer to allow the connection refresh to complete
Start-Sleep -Seconds 15
$newFileName = "DSR.xlsx"
$ReportName = "DSR"
# Save the workbook with the current date
if (Test-Path "C:\ScheduledReports\DSR\DSRDailyUpdateFile\$newFileName") {
# Overwrite the existing file
[System.IO.File]::Delete("C:\ScheduledReports\DSR\DSRDailyUpdateFile\$newFileName")
}
# Remove the queries as they are not needed in the new file
foreach ($query in $workbook.Queries) {
$query.Delete()
}
# Save the workbook as a new file
$excel.DisplayAlerts = $false
$workbook.SaveAs("C:\ScheduledReports\DSR\DSRDailyUpdateFile\$newFileName")
#pic routine
$ConvertParam = @{
Path = 'C:\ScheduledReports\DSR\DSRBaseFile\DSRBaseFile.xlsx'
WorksheetName = 'Sheet3'
Range = 'A1:E7'
Destination = 'C:\ScheduledReports\DSR\picture\DSR.jpg'
}
Convert-ExcelRangeToImage @ConvertParam
# Close the workbook and Excel application
$workbook.Close()
$excel.Quit()
# Create and send an email
$image = 'C:\ScheduledReports\DSR\picture\DSR.jpg'
$ol = New-Object -ComObject Outlook.Application
$att1 = new-object Net.Mail.Attachment($Image)
$att1.ContentType.MediaType = “image/jpg”
$att1.ContentId = “Attachment”
$mail = $ol.CreateItem(0)
$mail.To = ""
$mail.CC = "me@company.com"
$mail.Subject = "DSR"
###$mail.IsBodyHTML = $true
# Attach the file to the email if it exists
###if (Test-Path "C:\ScheduledReports\DSR\DSRDailyUpdateFile\$newFileName") {
### $mail.Attachments.Add("C:\ScheduledReports\DSR\DSRDailyUpdateFile\$newFileName")
###$mail.Attachments.Add("C:\ScheduledReports\DSR\picture\DSR.jpg")
###}
$messageBody = @"
<html>
<head></head>
<body>
<p>See attached $ReportName.</p>
<img id="1" src="cid:DSR.jpg">
<p>Please do not reply to this email.<br>
For questions or concerns reach out to me@company.com</p>
</body>
</html>
"@
$ol = New-Object -ComObject Outlook.Application
$mail = $ol.CreateItem(0)
$mail.To = ""
$mail.CC = "me@company.com"
$mail.Subject = "DSR"
$mail.HTMLBody = $messageBody
# Attach the file to the email if it exists
if (Test-Path "C:\ScheduledReports\DSR\DSRDailyUpdateFile\$newFileName") {
$mail.Attachments.Add("C:\ScheduledReports\DSR\DSRDailyUpdateFile\$newFileName")
$mail.Attachments.Add("C:\ScheduledReports\DSR\picture\DSR.jpg")
}
$mail.Send()
# Add a wait timer to keep Outlook open long enough to send the email
Start-Sleep -Seconds 15
$excel.Quit()
# Close Outlook if it wasn't running before the script
###if (!$outlookWasRunning) {
### $ol.Quit()
###}
Sorry guys, I should have unchecked it as the “final” solution. Matt did get me what I asked for but when I went back and started to look again a bit later, I noticed that the picture was not being overwritten (again, I wasn’t clear at first and take the blame for that).
So what I did just now is uncheck that it’s the solution (when technically it’s like 95% of the final solution). Again, my apologies. I’m a total noob!!! (both in PowerShell AND this forum). If there is a point system on the forum, I believe matt-bloomfield should get them.
So, my hopefully final ask is to get the script to always overwrite the picture and get it embedded into the email body. I feel what I have now is so very close, but as I wrote in my last post, I think there’s possibly extra stuff in the script that isn’t needed as well as some little extra thing that would always overwrite the picture each time the script is run.
No problem. Before you unchecked it, I think it was your own post that was marked as the solution anyway
I cannot replicate the picture not being replaced. I don’t even have to delete it, Convert-ExcelRangeToImage just overwrites an existing file with the new image.
Are you getting any errors?
Are you definitely running it under PowerShell 5.1? As previously mentioned the cmdlet doesn’t work in PowerShell 7+ so if you have a test image or one previously created in PowerShell 5.1 and are now using version 7, because the cmdlet is failing there won’t be a new image.
Regarding cleaning up your code, a few pointers:
Avoid using the COM object, and instead use the ImportExcel module for all Excel tasks.
Stick to native PowerShell where possible, e.g. you don’t need [System.IO.File]::Delete(), use Remove-Item instead.
You have unused variables in your script: $image and $outlookWasRunning (now that you’ve commented that out). Using an editor like VSCode will highlight small errors like this and help keep your code tidy.