I am trying to automate some replies to email that I get on my outlook. I have tried sending mail from my outlook (normal mail) using powershell and it worked successfully. Now I am trying to reply on mail using powershell. This is my current code as of now:
$o = New-Object -com Outlook.Application
$all_mail = $o.Session.Folders.Item($myEmailId).Folders.Item("Inbox").Items
foreach ($mail in $all_mail) {
if ($mail.subject -match "Re: Testing") {
$reply = $mail.reply()
$reply.body = $reply.body + "Adding this extra info in mail."
$reply.send()
}
}
#myEmailId is my emailId, if trying this script, replace it with yours.
When I run this, I am getting the following error
Operation aborted (Exception from HRESULT: 0x80004004 (E_ABORT))
At line:7 char:13
+ $reply.send()
+ ~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMException
I have printed the logs in between while debugging and found that it is successfully picking up all the emails in my outlook. The if condition where it matches the mail subject is also working fine. I tried going through various resource on internet but could not find any resolution for this. Any help or direction will be really helpful.
Helping myself with the Microsoft Dev Blog :
Add-Type -assembly "Microsoft.Office.Interop.Outlook"
Add-type -assembly "System.Runtime.Interopservices"
try
{
$outlook = [Runtime.Interopservices.Marshal]::GetActiveObject('Outlook.Application')
$outlookWasAlreadyRunning = $true
}
catch
{
try
{
$Outlook = New-Object -comobject Outlook.Application
$outlookWasAlreadyRunning = $false
}
catch
{
write-host "You must exit Outlook first."
exit
}
}
$namespace = $Outlook.GetNameSpace("MAPI")
$inbox = $namespace.GetDefaultFolder([Microsoft.Office.Interop.Outlook.OlDefaultFolders]::olFolderInbox)
$mails = $inbox.Items | Where-Object {$_.Subject -like "ABC TEST*"}
foreach($mail in $mails) {
$reply = $mail.reply()
$reply.body = "TEST BODY"
$reply.send()
while(($namespace.GetDefaultFolder([Microsoft.Office.Interop.Outlook.OlDefaultFolders]::olFolderOutbox)).Items.Count -ne 0) {
Start-Sleep 1
}
}
# Kill Process Outlook (close COM)
Get-Process "*outlook*" | Stop-Process –force
The while loop on the Items.Count is used to check if the OutBox is empty or not. If you close the Outlook Process before it's done, your mail won't be sent.
Related
I have trying to extract attachments from Outlook which are matching the wildcard of senderemailaddress attribute. As can be seen in the below code, I was trying out with two filters but to no avail.
When I use uncommented filter currently active in the code, the code doesn't throw any errors nor does it download the attachments matching the test case. However if I activate the commented filter and run it, I get the following error.
Exception calling "Restrict" with "1" argument(s): "Cannot parse condition. Error at
"like"."
At C:\Users\acer\Desktop\outlook.ps1:42 char:2
+ $filteredItems = $folder.items.Restrict($filter)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation
Code:
$filepath = "C:\folder\subfolder\subsubfolder\"
function downloadFiles {
$filter = "[UnRead]=true AND [SenderEmailAddress] -match #example"
#$filter = "[UnRead]=true AND [SenderEmailAddress] -like '*#example*'"
Add-Type -Assembly "Microsoft.Office.Interop.Outlook" | Out-Null
$olFolders = "Microsoft.Office.Interop.Outlook.olDefaultFolders" -as [type]
$outlook = New-Object -ComObject Outlook.Application
$namespace = $outlook.GetNameSpace("MAPI")
$folder = $namespace.GetDefaultFolder($olFolders::olFolderInBox)
#$folder.Items | select SenderEmailAddress
$filteredItems = $folder.Items.Restrict($filter)
foreach ($objMessage in $filteredItems) {
$intCount = $objMessage.Attachments.Count
if ($intCount -gt 0) {
for ($i=1; $i -le $intCount; $i++) {
$objMessage.Attachments.Item($i).SaveAsFile($filepath+$objMessage.Attachments.Item($i).FileName)
}
}
$objMessage.Unread = $false
}
$outlook.Close
}
downloadFiles
Edit1 : Thanks everyone for the suggestions.
I was able to do it by filtering with unread = true and pattern matching the senderemailaddress from the properties of the filtered mails.
Adding the modified code:
$filepath = "C:\folder\subfolder\subsubfolder\"
function downloadFiles {
$filter="[UnRead]=true"
$emailfilter = "*#xyz.co.in"
$subjectfilter = "test file*"
Add-Type -Assembly "Microsoft.Office.Interop.Outlook" | Out-Null
$olFolders = "Microsoft.Office.Interop.Outlook.olDefaultFolders" -as [type]
$outlook = New-Object -ComObject Outlook.Application
$namespace = $outlook.GetNameSpace("MAPI")
$folder = $namespace.GetDefaultFolder($olFolders::olFolderInBox)
#$folder.Items | select SenderEmailAddress
$filteredItems = $folder.Items.Restrict($filter)
foreach ($objMessage in $filteredItems) {
$subject = $objMessage.Subject
$emailaddress = $objMessage.SenderEmailAddress
if(($emailaddress -like $emailfilter) -and ($subject -like $subjectfilter)){
$intCount = $objMessage.Attachments.Count
if ($intCount -gt 0) {
for ($i=1; $i -le $intCount; $i++) {
$objMessage.Attachments.Item($i).SaveAsFile($filepath+$objMessage.Attachments.Item($i).FileName)
}
}
$objMessage.Unread = $false
}
else {continue}
}
$outlook.Close
}
downloadFiles
Now the problem is scheduling this script? When I run this script using the powershell path in command prompt it's working fine. But when I schedule the same it's not completing. I could see the outlook process generated by the task scheduer in TaskManager and have to manually kill the process to terminate the same. Any ideas?
I'd use EWS. Saves having to allow programmatic access to Outlook.
Easiest way is to download from nuget. You can do this in PowerShell by first downloading nuget:
$sourceNugetExe = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
$targetNugetExe = "D:\Program Files\nuget\nuget.exe" # chaneg path to suit
Invoke-WebRequest $sourceNugetExe -OutFile $targetNugetExe
Set-Alias nuget $targetNugetExe -Scope Global -Verbose
Then download the EWS nuget package:
Set-Location D:\Temp # change to suit
nuget install 'Microsoft.Exchange.WebServices'
Now you can start using :)
# load the assembly
[void][Reflection.Assembly]::LoadFile("D:\Temp\Microsoft.Exchange.WebServices.2.2\lib\40\Microsoft.Exchange.WebServices.dll")
# set ref to exchange - may need to change the version
$s = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2)
# replace with your email address
$email = "your.email#domain.com"
# grab your own credentials
$s.UseDefaultCredentials = $true
# discover the url from your email address
$s.AutodiscoverUrl($email)
# get a handle to the inbox
$inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($s,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)
#create a property set (to let us access the body & other details not available from the FindItems call)
$psPropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropertySet.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text
$items = $inbox.FindItems(100) # change to suit
# loop through the emails - at this point, we don't have full info - we have to Load the email
# restrict on what we do know - if the email is read and if it has attachments
$items | where { !$_.IsRead -and $_.HasAttachments } | ForEach-Object {
# load the email, so we can get to other properties, like attachments, sender, etc
$_.Load()
# does the sender match our wildcard?
if ($_.Sender -like '*lmnopqr*') {
# loop through all file attachments
$_.Attachments | Where-Object { $_ -is [Microsoft.Exchange.WebServices.Data.FileAttachment] } | ForEach-Object {
# save them (yes, Load = Save in this instance!)
$_.Load("D:\Temp\$($_.Name)")
}
}
}
See the EWS link for more info on how to interact with EWS.
Also, see my other SO post which is out of date for where to get the EWS assembly from, but does have some useful info on extra EWS methods/properties. It also has details on how to use alternative credentials, if you're not using your own (or the process runing PowerShell doesn't have an Exchange account).
The provider does not allow the use of Like in the filter for this method. From this MSDN article:
There is no way to perform a "contains" operation. For example, you
cannot use Find or Restrict to search for items that have a particular
word in the Subject field. Instead, you can use the AdvancedSearch
method, or you can loop through all of the items in the folder and use
the InStr function to perform a search within a field.
I have following powershell script which should update my windows OS everytime I run it. Therefore I use the given windows API in order to search, download and install the updates. But somehow only searching for them actually works.
This is my script:
$global:scriptpath = $MyInvocation.MyCommand.Path
$global:dir = Split-Path $scriptpath
$global:logfile = "$dir\updatelog.txt"
write-host " Searching for updates..."
$session = New-Object -ComObject Microsoft.Update.Session
$searcher = $session.CreateUpdateSearcher()
$result = $searcher.Search("IsInstalled=0 and Type='Software' and IsHidden=0")
if ($result.Updates.Count -eq 0) {
Write-Host "No updates to install"
} else {
$result.Updates | Select Title
}
$downloads = New-Object -ComObject Microsoft.Update.UpdateColl
foreach ($update in $result){
try {
$update.AcceptEula()
$Null = $downloads.Add($update)
} catch {}
}
$count = $result.Updates.Count
write-host ""
write-host "There are $($count) updates available."
write-host ""
read-host "Press Enter to download\install updates"
$downloader = $session.CreateUpdateDownLoader()
$downloader.Updates = $downloads
$downloader.Download()
$installs = New-Object -ComObject Microsoft.Update.UpdateColl
foreach ($update in $result.Updates){
if ($update.IsDownloaded){
$installs.Add($update)
}
}
$installer = $session.CreateUpdateInstaller()
$installer.Updates = $installs
$installresult = $installer.Install()
$installresult
But I get following error:
Exception calling "Download" with "0" argument(s): "Exception from HRESULT: 0x80240024"
+ $downloader.Download()
+ ~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation
Somehow this line: $downloader.Updates = $downloads is not executed, but I don't know why. I also already tried running the script as an admin, didn't work either.
That error code is the WU_E_NO_UPDATE, described here. Basically it says that the Updates collection is not set or empty.
I can create an email and display it with my script, but for some reason it doesn't send and I receive the following error. Am I missing something, maybe there's a permissions issue?
Exception calling "Send" with "0" argument(s): "Operation aborted (Exception from HRESULT: 0x80004004 (E_ABORT))"At C:\TEMP\Scripts\PowerShell\Outlook EMail Creation\TestEMailSend.ps1:27 char:5
+ $mail.Send()
+ ~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation
My code:
$global:UserReportsToEmail = "my.email#domain.com"
$ol = New-Object -comObject Outlook.Application
$mail = $ol.CreateItem(0)
$mail.To = "$global:UserReportsToEmail"
$mail.cc = "EMAIL#domain.com"
$mail.Subject = "mySubject"
$mail.HTMLBody =
"<font color ='blue'><b>TESTING STUFFFF!</b></font><br>
Text on a new line $UserID"
$mail.Send()
$inspector = $mail.GetInspector
$inspector.Display()
According to a few of the Microsoft sites (e.g. https://social.msdn.microsoft.com/Forums/en-US/80c66a08-66ee-4ab6-b629-6b1e70143eb0/operation-aborted-exception-from-hresult-0x80004004-eabort-outlook-appointment?forum=outlookdev ) this is due to 'object model guard'. A security feature to prevent automated programs from doing stuff like auto-emailing out viruses and stuff from the background.
You probably already worked this out. Posting this here so that others like myself can more quickly and easily understand why its not working.
you can use the Send-MailMessage CmdLets : https://technet.microsoft.com/en-us/library/hh849925.aspx
then when i need more controls and dispose functionnality i use System.Net.Mail.SmtpClient
try
{
$emailCredentials = Import-Clixml "C:\testMail\credentials.clixml"
$recipients = #("user#mail.com", "user2#mail.com", "user3#mail.com")
$attachments = #("C:\testMail\file.txt", ""C:\testMail\file2.txt", "C:\testMail\file3.txt")
# create mail and server objects
$message = New-Object -TypeName System.Net.Mail.MailMessage
$smtp = New-Object -TypeName System.Net.Mail.SmtpClient($buildInfoData.BuildReports.Mail.Server)
# build message
$recipients | % { $message.To.Add($_) }
$message.Subject = $subject
$message.From = New-Object System.Net.Mail.MailAddress($emailCredentials.UserName)
$message.Body = $mailHtml
$message.IsBodyHtml = $true
$attachments | % { $message.Attachments.Add($(New-Object System.Net.Mail.Attachment $_)) }
# build SMTP server
$smtp = New-Object -TypeName System.Net.Mail.SmtpClient(smtp.googlemail.com)
$smtp.Port = 572
$smtp.Credentials = [System.Net.ICredentialsByHost]$emailCredentials
$smtp.EnableSsl = $true
# send message
$smtp.Send($message)
Write-Host "Email message sent"
}
catch
{
Write-Warning "$($_.Exception | Select Message, Source, ErrorCode, InnerException, StackTrace | Format-List | Out-String)"
}
finally
{
Write-Verbose "Disposing Smtp Object"
$message.Dispose()
$smtp.Dispose()
}
I have a script that goes into SCSM, gets the relevant files from the IR and downloads them properly onto the user's computer.
Next, I need the script to create an email from the client's outlook application and display it on their screen before sending the email..
Here is the part of the script that does that:
$GetFiles = Get-ChildItem "$ArchiveRootPath\$IRID\"
$ol = New-Object -comObject Outlook.Application #| New-Object Net.Mail.MailMessage
$mail = $ol.CreateItem(0)
$mail.To = "johndoe#contoso.com"
$mail.Subject = "This is a test [$IRID]"
Foreach($GetFile in $GetFiles)
{
Write-Host “Attaching File :- ” $GetFile
$attachment = New-Object System.Net.Mail.Attachment –ArgumentList C:\Temp\SCSM_Archive\$IRID\$GetFile
$mail.Attachments.Add($attachment)
}
$inspector = $mail.GetInspector
$inspector.Display()
However, when I run the script, I get this error:
Attaching File :- email.eml
Exception calling "Add" with "1" argument(s): "Value does not fall within the expected range."
At C:\Temp\SSWireless Script.ps1:99 char:2
+ $mail.Attachments.Add($attachment)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation
All the variables are checked from the previous part of the script that downloads the files. PS is able to see the files, but cannot attach them to the email.
I don't want to use the send-message function due to the fact that we will be moving to a cross domain email account and would rather use outlook to create the email itself.
Here is a reference for Microsoft.Office.Interop.Outlook Attachments.Add method . This confirms that the first parameter (and the only parameter in your case) needs to be a full path to file.
There is no point passing [System.Net.Mail.Attachment] .NET objects to COM interface.
Your code should look something like this:
$GetFiles = Get-ChildItem "$ArchiveRootPath\$IRID\"
$ol = New-Object -comObject Outlook.Application
$mail = $ol.CreateItem(0)
$mail.To = "johndoe#contoso.com"
$mail.Subject = "This is a test [$IRID]"
Foreach($GetFile in $GetFiles)
{
Write-Host “Attaching File :- $GetFile.Name"
$mail.Attachments.Add($GetFile.FullName)
}
How is it possible to save an e-mail on disk with the help of EWS in PowerShell? I've searched the internet and found some answers, but that's all for C# or VB.
The code I have now does everything I need, copy the e-mail to the correct folder in MS Outlook and so on, but I can't seem to figure out how to save the message in a folder ($ENV:Temp) on disk.
This can be in the format EML or MSG, that doesn't matter to me, but it needs to be saved with everything it has (body, attachments, From, To, ..).
I've tried $Mail | Out-File "$env:TEMP\test.eml", and it does indeed generate a 15 KB file but it appears to be empty when I open it with MS Outlook.
Thank you for your help.
In the meantime, I found the solution:
$Service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService -ArgumentList $ExchangeVersion
$Service.Credentials = $Credentials.GetNetworkCredential()
$Service.AutodiscoverUrl($BNLMailbox)
Try {
$PowerShellPathId = Find-MailFolderIDHC #FindMailParams -Path $BNLMailboxInbox
$Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service,$PowerShellPathId)
}
Catch {
# Exchange version not correct or path not found
throw "Move-MailsHC $($Global:Error[0].Exception.Message)"
}
$Props = [Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties
$PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet($Props)
$PropertySet.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text
$PropertySet.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::MimeContent)
$Date = [Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived
$TimeSpan = (Get-Date).AddHours(-$HoursAgo)
$Filter = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+IsGreaterThan -ArgumentList $Date,$TimeSpan
$View = New-Object Microsoft.Exchange.WebServices.Data.ItemView(100)
$View.OrderBy.add([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived,
[Microsoft.Exchange.WebServices.Data.SortDirection]::Ascending)
foreach ($Mail in $Mails.Items) {
$TmpFolder = Join-Path $env:TEMP 'Move-MailsHC'
if (-not(Test-Path $TmpFolder)) {
New-Item $TmpFolder -ItemType Directory | Out-Null
}
Write-Verbose "Save original e-mail in temp '$TmpFolder'"
$TmpMail = Join-Path $TmpFolder 'Mail.eml'
$IoFile = New-Object System.IO.FileStream($TmpMail, [System.IO.FileMode]::Create)
$IoFile.Write($Mail.MimeContent.Content, 0, $Mail.MimeContent.Content.Length)
$IoFile.Close()
Write-Verbose "Download e-mail attachments to temp '$TmpFolder'"
foreach ($A in $Mail.Attachments){
$A.Load()
$fiFile = New-Object System.IO.FileStream((Join-Path $TmpFolder $A.Name.ToString()), [System.IO.FileMode]::Create)
$fiFile.Write($A.Content, 0, $A.Content.Length)
$fiFile.Close()
Write-Verbose "Downloaded Attachment: $($A.Name.ToString())"
}
}