I am trying to write the sender/subject info of all the emails in my outlook inbox to a csv file and then move the emails to a subfolder of my inbox (called "After") using Powershell. The CSV file is created correctly with the email info, but only the first half + 1 emails are actually moved to the subfolder. Here is my code:
$olFolderInbox = 6;
$outlook = new-object -com outlook.application;
$mapi = $outlook.GetNameSpace("MAPI");
$inbox = $mapi.GetDefaultFolder($olFolderInbox);
$inbox.items|Select SenderEmailAddress,subject|Export-Csv C:\Scripts\Testing.csv -NoTypeInformation;
ForEach ($Item in $inbox.items){
try{
$Item.Move($inbox.Folders.Item("After")) | out-null;
}catch{
Write-Host "Failed to move item", $Item.Id.UniqueId;
}
}
This is my first time using Powershell so any help is much appreciated!
Move() changes the collection. Use a down "for" loop (from items.Count down to 1) instead of "foreach".
Related
I'm trying to write a PowerShell script that automates the way to retrieve all my emails with sender information in outlook and importing it on a text file.
I monitored this script that I created returns incomplete results.
Below here is my code for:
$namespace = $Outlook.GetNameSpace("MAPI")
$inbox = $namespace.GetDefaultFolder([Microsoft.Office.Interop.Outlook.OlDefaultFolders]::olFolderInbox)
$emails = $inbox.items
ForEach ($email in $emails){
write-host $email.Subject
}
If "incomplete results" means that it's not returning all the emails you're expecting, there are a couple of things that I ran into when working with emails in Powershell:
It won't grab emails that are in folders under the Inbox. You have to call each folder separately. I had to setup a recusive loop to compile a list of them
Not all of your emails are actually stored in Outlook. By default, Outlook only pulls the last year of email form an email server. Sometimes it can show messages that exist on the server but they aren't actually downloaded.
EDIT: Here's the recursive function I built to get all the folders and subfolders within the Inbox.
# Create an ArrayList and immediately add the Inbox as the first folder in the list
[System.Collections.ArrayList] $folderList = #([PSCustomObject]#{
FolderPath = $inbox.FolderPath
EntryID = $inbox.EntryID
})
# Call the function to get all the folders and subfolders in the Inbox folder
Get-MailFolders $inbox.Folders
# Recusive function that will get all the folders and subfolders in the parent folder
function Get-MailFolders ($parent) {
foreach ($child in $parent) {
Write-Host "." -NoNewLine
$folderList.Add([PSCustomObject]#{
FolderPath = $child.FolderPath
EntryID = $child.EntryID
}) | Out-Null
Get-MailFolders ($child.Folders)
}
}
Continuing form my comment
Search for:
'PowerShell read outlook email name and subject'
hit(s)
https://devblogs.microsoft.com/scripting/use-powershell-to-data-mine-your-outlook-inbox
Read most recent e-mail from outlook using PowerShell
http://jon.glass/blog/reads-e-mail-with-powershell
olFolderInbox = 6
$outlook = new-object -com outlook.application;
$mapi = $outlook.GetNameSpace("MAPI");
$inbox = $mapi.GetDefaultFolder($olFolderInbox)
# Grab the specific properties from the messages in the Inbox:
$olFolderInbox = 6
$outlook = new-object -com outlook.application;
$mapi = $outlook.GetNameSpace("MAPI");
$inbox = $mapi.GetDefaultFolder($olFolderInbox)
$inbox.items|Select SenderEmailAddress,to,subject|Format-Table -AutoSize
# Results
<#
SenderEmailAddress To Subject
------------------ -- -------
notify#twitter.com Jonathan Glass Thomas Garnier (#mxatone) retweeted one...
mailing-list#rifftrax.com Riff Rediscover Puppets in our latest short!
notify#twitter.com Jonathan Glass [ Gunther ] (#Gunther_AR) retweeted one...
#>
I am trying to select multiple emails from on outlook inbox folder via mapi addressing and want to move a copy of these emails to another folder in the same inbox.
Unfortunately my script seems to do whatever it wants, sometimes copying 6 emails before stopping with following failure, sometimes stopping right with the first email.
Failure:
... "veeam")} | ForEach-Object {$_.Copy().Move($Namespace.Folders.Item("$ ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [ForEach-Object], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMException,Microsoft.PowerShell.Commands.ForEachObjectCommand
I could not find any solution for this and I am sitting here confused since in another mailbox the code works just fine.
Of course I am setting the variables $Mailbox and $TempWorkPath beforehand.
Thanks in advance for your help.
Trying to run the code in a foreach-loop is less performant and ends with the same issue.
About 3 hours of google search did not help me at all.
Just moving the object causes the code to break, probably because of indexiation?
Add-Type -Assembly "Microsoft.Office.Interop.Outlook"
$OutlookSession = New-Object -ComObject Outlook.Application
$Namespace = $OutlookSession.GetNameSpace("MAPI")
$Namespace.Folders.Item("$Mailbox").Folders.Item("Posteingang").Items.Restrict('[UnRead] = True') | Where-Object {($_.Subject -match "ackup") -or ($_.SenderEmailAddress -match "veeam")} | ForEach-Object {$_.Copy().Move($Namespace.Folders.Item("$Mailbox").Folders.Item("Posteingang").Folders.Item("$TempWorkPath"))} | Out-Null
<# Do things with the selected/coppied emails #>
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($OutlookSession) | Out-Null
$OutlookSession = $null | Out-Null
In Theory an based on my tests in another folder this should work perfectly fine, create a copy of the email, move it to my folder and afterwards I can do things with it.
Well, I think I found my way around the issue. Running the command in a while loop instead of an foreach loop seems to work better.
$Inbox = $Namespace.Folders.Item("$Mailbox").Folders.Item("Posteingang").Items.Restrict('[UnRead] = True') | Where-Object {($_.Subject -match "ackup") -or ($_.SenderEmailAddress -match "veeam")}
$MailCounter = $Inbox.Count
$HelperForCounting = 0
while ($MailCounter -gt $HelperForCounting)
{
$Inbox[$MailCounter].Copy().Move($Namespace.Folders.Item("$Mailbox").Folders.Item("Posteingang").Folders.Item("$TempWorkPath"))
$MailCounter = $MailCounter - 1
}
Greetings
I also had this issue with processing emails on Outlook. My overall scheme is to process emails folder by folder. I traced the issue to the Emails.getNext() function. My completely uneducated guess is it has something to do with parallel processing of Emails and how it grabs them in ForEach() and getNext(). The problem went away by using the getLast().
Note in the following code it will just move all read emails to archive folder and then some unread emails to corporate dump folder and most unread emails to the unread folder. This is itself just a mutation on the .p0r email script. There is a > $null at the end of the function block is where I originally had it on the ForEach loop and it worked as one would expect, but it does not work on the While loop blocking function. Instead that had to be moved to the location in the move unread section. Still a lot of room for improvement, getting some strange com errors but it will process through an inbox so long as GetLast() email is moved out of the folder.
As for my rationale on the root cause, I noticed that the failure to read a whole inbox is dependent on the size of the inbox. So each run my go through 2/3 of the remaining emails in the inbox.
# OUTLOOK RULES #
#################
# OUTLOOK RULES #
#################
#Import Object Library?
Add-Type -assembly "Microsoft.Office.Interop.Outlook"
# VARIABLES
$index=0;
$pstPath = "C:\YOURPATHHERE"
# DISPLAY INFO
function display( [string]$subject, [string]$color , [string]$out) {
# REQUIRED LENGTH OF STRING
$len = 20
# STRINGS THAT ARE LONGER WILL BE CUT DOWN,
# STRINGS THAT ARE TO SHORT WILL BE MADE LONGER
if ( $subject.length -lt 20 ){
$toadd=20-$subject.length;
for ( $i=0; $i -lt $toadd; $i++ ){
$subject=$subject+" ";
}
$len = $subject.length
}
else { $len = 20 }
$index=$index+1
Write-host -ForegroundColor $color -nonewline " |" ((($subject).ToString()).Substring(0,$len)).ToUpper()
}
# CREATING OUTLOOK OBJECT
$outlook = New-Object -comobject outlook.application
$namespace = $outlook.GetNameSpace("MAPI")
# GETTING PST FILE THAT WAS SPECIFIED BY THE PSTPATH VARIABLE
$pst = $namespace.Stores | ?{$_.FilePath -eq $pstPath}
# ROOT FOLDER
$pstRoot = $pst.GetRootFolder()
# SUBFOLDERS
$pstFolders = $pstRoot.Folders
$fArchive = $pstFolders.Item("Archive")
# PERSONAL SUBFOLDER
$personal = $pstFolders.Item("Personal")
# INBOX FOLDER
$DefaultFolder = $namespace.GetDefaultFolder(6)
# INBOX SUBFOLDERS
$InboxFolders = $DefaultFolder.Folders
# DELETED ITEMS
$DeletedItems = $namespace.GetDefaultFolder(3)
# EMAIL ITEMS
$Emails = $DefaultFolder.Items
$workingFile = [IO.Path]::GetTempFileName()
# PROCESSING EMAILS
$currentWriteFolder = $pstFolders.Item("Archive")
While ($Emails.count -gt 0) {
$Email = $Emails.GetLast()
#Move all reads into Archive
if (!$Email.Unread) {
$email.move($fArchive) > $null
continue
}
#Filter unread items by sender
$WriteString = $Email.SenderEmailAddress.ToString()
[IO.File]::WriteAllLines($workingFile, $WriteString)
if (Select-String -Path $workingFile -Pattern "company") {
$email.move($currentWriteFolder.Folders.Item("globalcorp"))
continue
}
$email.move($pstFolders.Item("Unread"))
} # > $null
[IO.File]::Delete($workingFile)
Write-host ""
I am using following code to download outlook email attachments and images to folder. But it is throwing an error:
Cannot index into a null array.
Any guess why?
$o = New-Object -ComObject outlook.Application
$ns = $o.GetNamespace("MAPI")
$f = $ns.Folders.Item(1)
$di = $f.Folders.item("Deleted Items")
$messagesWithAttachments = $di.items | Where-Object {$_.Attachments.Count -gt 0}
$messagesWithAttachments[0].Attachments.item(1).saveasfile("C:\test")
Provided there are emails with attachments, give a filename instead of folder in the saveasfile() method.
Eg: saveasfile("C:\test\test.txt")
I'm searching for a way to automate using Outlook's Export to PST functionality in order to pull Public Folders named A-Z as individual PST files (because there is a PST size limit) and I'm hoping for a pure Powershell method.
What I have found so far isn't automated but it helped when I had to get someone's folder copied over quickly.
Add-Type -assembly "Microsoft.Office.Interop.Outlook"
$Outlook = New-Object -ComObject Outlook.Application
$namespace = $Outlook.GetNameSpace("MAPI")
#Get the public folder ID
$SourceFolder = $namespace.PickFolder() | Select EntryID
#Get the destination folder ID
$DestinationFolder = $namespace.PickFolder() | Select EntryID
$namespace.GetFolderFromID($SourceFolder).CopyTo($DestinationFolder)
The second post here https://serverfault.com/questions/180916/export-exchange-public-folder-to-pst-from-powershell looked helpful but I'm running into errors with the AutoDiscover URL and/or likely permission problems.
Also found this which looked but I don't want PSTs attached to my outlook.
Any pointers would be much appreciated!
Copy Exchange on-prem public folder to Local Outlook PST file.
Thanks to: https://github.com/misterGF/DevOps-PowerShell/blob/master/export-PFdata.ps1
https://www.vistax64.com/threads/looping-through-outlook-folders-and-subfolders-returning-numberof-e-mails.249828/
How to disconnect PST file from Outlook using Powershell?
Managed to work on this and got something functional, please use at your OWN risk. No error handling is performed.
function Get-MailboxFolder($folder)
{
Begin{
$Outlook = New-Object -ComObject Outlook.Application
$namespace = $Outlook.GetNameSpace("MAPI")
}
Process{
New-Item -Path 'E:\PST.archive\Full Backup\Enterprise West' -Name "$($folder.Name)" -ItemType Directory
foreach ($f in $folder.folders) {
#Location of PST files
$PSTPath = 'E:\PST.archive\Full Backup\Enterprise West' + '\' + "$($folder.Name)"
$PSTName = $($f.name) + '.pst'
$FullPST = $PSTPath + '\' + $PSTName
$namespace.AddStore("$FullPST")
$pstFolder = $namespace.Session.Folders.GetLast()
"Start Public Folder copy to local PST"
[void]$f.CopyTo($pstFolder)
}
}#End process block
End{
"Removing attached PSTs from Outlook"
$RemPST = $Outlook.Session.Stores | Where DisplayName -EQ 'Outlook Data File'
foreach ($pst in $RemPST){
$Outlook.Session.RemoveStore($pst.GetRootFolder())
}
}#end End Block
}
$WestFolders = $Outlook.Session.Folders.Item('Public Folders - YourNameHERE!!!!!').folders.item('All Public Folders').Folders.Item('Level-1').folders.item('Level-2').folders.item('Level-3')
Get-MailboxFolder $WestFolders
There is no automate method to export a public folder to a PST:
Export-Mailbox command won't work with public folders.
You can do it by using:
1) use outlook : Import/Export wizard
2) use third party tools
How to accept confirmation Automatically in PowerShell for Outlook
I have script for Export attachments from email from Outlook - see next
It works correctly on one PC, but on another PC is there a problem:
Outlook gives message and wants answer:
Permit Denny Help
If I manually click on Permit or Denny it works correctly. I want to automate it.
Can you give me some suggestion how to do it in PowerShell?
I have tried to set Outlook to not give this message but I didn’t success.
My script:
# <-- Script --------->
# script works with outlook Inbox folder
# check if email have attachments with ".txt" and save those attachments to $filepath
# path for exported files - attachments
$filepath = "d:\Exported_files\"
# create object outlook
$o = New-Object -comobject outlook.application
$n = $o.GetNamespace("MAPI")
# $f - folder „dorucena posta“ 6 - Inbox
$f = $n.GetDefaultFolder(6) # 6 - Inbox
# select newest 10 emails, from it olny this one with attachments
$f.Items| select -last 10| Where {$_.Attachments}| foreach {
# process only unreaded mail
if($_.unread -eq $True) {
# processed mail set as read, not to process this mail again next day
$_.unread = $False
$SenderName = $_.SenderName
Write-Host "Email from: ", $SenderName
# process all attachments
$_.attachments|foreach {
$a = $_.filename
If ($a.Contains(".txt")) {
Write-Host $SenderName," ", $a
# copy *.txt attachments to folder $filepath
$_.saveasfile((Join-Path $filepath "$a"))
}
}
}
}
Write-Host "Finish"
# <------ End Script ---------------------------------->
I found that security prompt is generate on line
" $SenderName = $_.SenderName "
Realy I dont need to use SenderName and I deleted this line.
Now script works OK without any message.