Script to download emails from Exchange as HTML, Can't use outlook - powershell

I have a script already that can download email attachments using EWS to avoid having outlook installed/opened.
I'm now wanting to save email content, preferably as a HTML file, but otherwise as .eml or .msg.
My other thread about the attachments is here: Download attachments with multiple subjects from Exchange
I was hoping that the existing script could be adapted somehow, but honestly I have no idea.
$MailboxName = "me#mymail.com"
$Subjects = #(
'Subject1',
'Subject2'
)
[regex]$SubjectRegex = ‘^(?i)(‘ + (($Subjects |foreach {[regex]::escape($_)}) –join “|”) + ‘)$’
$downloadDirectory = "C:\temp"
Function FindTargetFolder($FolderPath){
$tfTargetidRoot = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)
$tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$tfTargetidRoot)
for ($lint = 1; $lint -lt $pfArray.Length; $lint++) {
$pfArray[$lint]
$fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+isEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$pfArray[$lint])
$findFolderResults = $service.FindFolders($tfTargetFolder.Id,$SfSearchFilter,$fvFolderView)
if ($findFolderResults.TotalCount -gt 0){
foreach($folder in $findFolderResults.Folders){
$tfTargetFolder = $folder
}
}
else{
"Error Folder Not Found"
$tfTargetFolder = $null
break
}
}
$Global:findFolder = $tfTargetFolder
}
$dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll"
[void][Reflection.Assembly]::LoadFile($dllpath)
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1)
$windowsIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$sidbind = "LDAP://<SID=" + $windowsIdentity.user.Value.ToString() + ">"
$aceuser = [ADSI]$sidbind
$uri=[system.URI] "https://webmail.com/EWS/Exchange.asmx"
$service.Url = $uri
FindTargetFolder($ProcessedFolderPath)
$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
$InboxFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
$Sfsub = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.ItemSchema]::Subject,$Subject[0])
$sfCollection = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::And);
$sfCollection.add($Sfsub)
$sfCollection.add($Sfha)
$view = new-object Microsoft.Exchange.WebServices.Data.ItemView(2000)
$frFolderResult = $InboxFolder.FindItems($sfCollection,$view)
foreach ($miMailItems in $frFolderResult.Items)
{
$miMailItems.body.text | set-content C:\temp
}

Using the foreach loop from your other script:
foreach ($miMailItems in $frFolderResult.Items)
{
$miMailItems.body.text | set-content <filepath>
}
You'll need to come up with a file naming scheme for the output files, but getting the email body text out to a file is relatively trivial.

Related

How to export XML of webpart on the page

I want to export the Web part XML based on GUID to the local. I am trying to export from SharePoint 2013 using CSOM PowerShell. Anyone suggest on this?
function EnsureDirectory($exportFolderPath)
{
if ( -not (Test-Path $exportFolderPath) ) {New-Item $exportFolderPath -Type Directory | Out-Null}
}
function ExportAllWebParts()
{
$WebURL= ""
$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($WebURL)
$Page = $ctx.web.GetFileByServerRelativeUrl("")
$ctx.Load($Page)
$ctx.ExecuteQuery()
$wpManager = $Page.GetLimitedWebPartManager([Microsoft.SharePoint.Client.WebParts.PersonalizationScope]::Shared)
$webparts = $wpManager.Webparts
$ctx.Load($webparts)
$ctx.ExecuteQuery()
if($webparts.Count -gt 0){
Write-Host "Looping through all webparts"
foreach($webpart in $webparts){
$exportPath = "" + "\" + $webpart.Title + ".xml"
$xwTmp = new-object System.Xml.XmlTextWriter($exportPath,$null);
$xwTmp.Formatting = 1;#Indent
$wpManager.ExportWebPart($webpart, $xwTmp);
$xwTmp.Flush();
$xwTmp.Close();
#$webpartXmlWriter = New-Object System.Xml.XmlTextWriter($fileName,$null)
#$webpartXmlWriter.Formatting = [System.Xml.Formatting]::Indented
#$wpManager.ExportWebPart($webpart,$webpartXmlWriter)
}
}
}
ExportAllWebParts

Download emails from outlook using powershell?

How do I reference a specific email address in this code it always directs to
the default email account. I have multiple email accounts in my outlook and I want to download emails from a different account which I want to reference by that email address . I have a feeling
$folder = $namespace.getDefaultFolder($olFolders::olFolderInBox)
has to be changed please give suggestions.
`[CmdletBinding(DefaultParameterSetName="All")] `
`Param(
[Parameter(Mandatory=$true,
Position=0,
HelpMessage='Folder path to store emails. Do not use quotation marks even if the path has spaces.',
ValueFromPipelineByPropertyName=$true
)]
[Alias("Destination", "Dest", "FullName")]
[String]$DestinationPath, `
[Parameter(ParameterSetName="All")]
[Parameter(Mandatory=$true,ParameterSetName="Unread")]
[Switch]$UnreadOnly,
[Parameter(ParameterSetName="Unread")]
[Switch]$MarkRead
)
#Removes invalid Characters for file names from a string input and outputs
the clean string
` #Similar to VBA CleanString() Method
#Currently set to replace all illegal characters with a hyphen (-)
Function Remove-InvalidFileNameChars {`
param(
[Parameter(Mandatory=$true, Position=0)]
[String]$Name
)
return [RegEx]::Replace($Name, "[{0}]" -f ([RegEx]::Escape([String][System.IO.Path]::GetInvalidFileNameChars())), '-')
}
#Test for destination folder nonexistence
if (!(Test-Path $DestinationPath)) {
#Set values for prompt and menu
$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", `
"Confirmation Choice"
$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", `
"Negative Response"
$options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
$title = "Invalid Destination"
$message = "The folder you entered does not exist. Would you like to create the folder?"
#Prompt for folder creation and store answer
$result = $host.UI.PromptForChoice($title, $message, $options, 0)
#If yes, create.
if ($result -eq 0) {
New-Item $DestinationPath -ItemType Directory | Out-Null
Write-Host "Directory created."
}
#If no, exit
else {exit}
}
#Add a trailing "\" to the destination path if it doesn't already
if ($DestinationPath[-1] -ne "\") {
$DestinationPath += "\"
}
#Add Interop Assembly
Add-type -AssemblyName "Microsoft.Office.Interop.Outlook" | Out-Null
#Type declaration for Outlook Enumerations, Thank you Hey, Scripting Guy! blog for this demonstration
$olFolders = "Microsoft.Office.Interop.Outlook.olDefaultFolders" -as [type]
$olSaveType = "Microsoft.Office.Interop.Outlook.OlSaveAsType" -as [type]
$olClass = "Microsoft.Office.Interop.Outlook.OlObjectClass" -as [type]
#Add Outlook Com Object, MAPI namespace, and set folder to the Inbox
$outlook = New-Object -ComObject Outlook.Application
$namespace = $outlook.GetNameSpace("MAPI")
#Future Functionality to Receive Email before saving - Still Needs Testing
#$outlook.Session | Out-Null
#$outlook.Session.SendAndReceive($false) | Out-Null
$folder = $namespace.getDefaultFolder($olFolders::olFolderInBox)
#Iterate through each object in the chosen folder
foreach ($email in $folder.Items) {
#Get email's subject and date
[string]$subject = $email.Subject
[string]$sentOn = $email.SentOn
#Strip subject and date of illegal characters, add .msg extension, and combine
$fileName = Remove-InvalidFileNameChars -Name ($sentOn + "-" + $subject + ".msg")
#Combine destination path with stripped file name
$dest = $DestinationPath + $fileName
#Test if object is a MailItem
if ($email.Class -eq $olClass::olMail) {
#Test if UnreadOnly switch was used
if ($UnreadOnly) {
#Test if email is unread and save if true
if ($email.Unread) {
#Test if MarkRead switch was used and mark read
if ($MarkRead) {
$email.Unread = $false
}
$email.SaveAs($dest, $olSaveType::olMSG)
}
}
#UnreadOnly switch not used, save all
else {
$email.SaveAs($dest, $olSaveType::olMSG)
}
}
}
Think You can do something like this:
$outlook = New-Object -ComObject Outlook.Application
$namespace =$outlook.GetNameSpace("MAPI")
$namespace.Logon("Profilename","profilepassword",$false,$false)
Also you can use Assembly - Microsoft.Exchange.WebServices.dll and do something like this:
[Reflection.Assembly]::LoadFile("C:\Program Files (x86)\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll") > $nul
$getref = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2)
$getref.Credentials = New-Object Net.NetworkCredential('Account', 'Password', 'domain.local')
$getref.AutodiscoverUrl("Account#domain.com")
$inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($getref,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)
#Write-Host "Total Messages:" $inbox.TotalCount
$psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView($inbox.TotalCount)
$fiItems = $getref.FindItems($Inbox.Id,$ivItemView)
[Void]$getref.LoadPropertiesForItems($fiItems,$psPropset)
foreach($Item in $fiItems.Items){
if ($Item.From -EQ "Somemail#domain.com") {
New-Object -TypeName PSObject -Property #{
Emails = $Item.From
} | select Emails
}
}
Hello! You can load a body text (to html) something like this:
foreach($Item in $fiItems.Items){
if ($Item.Subject -match "Something special") {
$Item.Load()
$Save = ((Get-Date -Format "yyMMdd") + "-" + $Item.Subject[0] + ".html")
New-Item -Path "C:\file\exch\" -name $Save -ItemType file -value $Item.Body.Text
}
}

How can I use EWS to move items to a folder?

The following PowerShell script as been adapted to our situation.
It read all emails in the Inbox folder then extract attachments
It working well but I would like to move items to the "/Processed" mailbox root folder. This folder is not a subfolder of the Inbox folder :
Mailbox
L Inbox
L Processed
L Sent Items
L Deleted Items
If I use the following line
[VOID]$miMailItems.Move("DeletedItems")
However, it doesn't work as expected. It deleted the email but in my personal mailbox, not the "john" mailbox !
So, can you help me to
correct the code to move items to the john mailbox when using the code [VOID]$miMailItems.Move("DeletedItems")
let me know how I can simply moving items to the john "Processed" mailbox subfolder?
$MailboxName = 'john#domain.com'
$downloadDirectory = '\\share\'
$dllpath = "C:\Program Files\Microsoft\Exchange Server\V15\Bin\Microsoft.Exchange.WebServices.dll"
[VOID][Reflection.Assembly]::LoadFile($dllpath)
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013)
$sidbind = "LDAP://<SID=" + (Get-ADUser exchadmin).SID.ToString() + ">"
$aceuser = [ADSI]$sidbind
$service.AutodiscoverUrl($aceuser.mail.ToString())
$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
$InboxFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
$Sfha = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::HasAttachments, $true)
$sfCollection = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::And);
$sfCollection.add($Sfha)
$view = new-object Microsoft.Exchange.WebServices.Data.ItemView(2000)
$frFolderResult = $InboxFolder.FindItems($sfCollection,$view)
foreach ($miMailItems in $frFolderResult.Items){
$miMailItems.Load()
foreach($attach in $miMailItems.Attachments){
$attach.Load()
$fiFile = new-object System.IO.FileStream(($downloadDirectory + “\” + (Get-Date).Millisecond + "_" + $attach.Name.ToString()), [System.IO.FileMode]::Create)
$fiFile.Write($attach.Content, 0, $attach.Content.Length)
$fiFile.Close()
}
$miMailItems.isread = $true
$miMailItems.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite)
# The following send items to my personal "Deleted Items" folder instead of the john mailbox...
[VOID]$miMailItems.Move("DeletedItems")
# How can I send items to the "/Processed" folder of the john mailbox ?
}
The Move method will take the FolderId of the folder you want to move the Item to so you need to first find the FolderId of the folder you want to move to eg
function FolderIdFromPath{
param (
$FolderPath = "$( throw 'Folder Path is a mandatory Parameter' )",
$SmtpAddress = "$( throw 'Folder Path is a mandatory Parameter' )"
)
process{
## Find and Bind to Folder based on Path
#Define the path to search should be seperated with \
#Bind to the MSGFolder Root
$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$SmtpAddress)
$tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
#Split the Search path into an array
$fldArray = $FolderPath.Split("\")
#Loop through the Split Array and do a Search for each level of folder
for ($lint = 1; $lint -lt $fldArray.Length; $lint++) {
#Perform search based on the displayname of each folder level
$fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$fldArray[$lint])
$findFolderResults = $service.FindFolders($tfTargetFolder.Id,$SfSearchFilter,$fvFolderView)
if ($findFolderResults.TotalCount -gt 0){
foreach($folder in $findFolderResults.Folders){
$tfTargetFolder = $folder
}
}
else{
"Error Folder Not Found"
$tfTargetFolder = $null
break
}
}
if($tfTargetFolder -ne $null){
return $tfTargetFolder.Id.UniqueId.ToString()
}
else{
throw "Folder not found"
}
}
}
#Example use
$fldId = FolderIdFromPath -FolderPath "\Processed" -SmtpAddress $aceuser.mail.ToString()
$SubFolderId = new-object Microsoft.Exchange.WebServices.Data.FolderId($fldId)
$SubFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$SubFolderId)
then just change
[VOID]$miMailItems.Move($SubFolder.Id)

EWS Not returning folder permissions

any ideas as to why this ews managed api in powershell keeps returning a 0 folder count and no permissions? i'm using impersonation, it's returning the folder names but no permissions.
function GetPerms{
param([string]$mailboxaddress)
$enumSmtpAddress = [Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress
$global:service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId($enumSmtpAddress,$mailboxaddress);
$mailbox = New-Object Microsoft.Exchange.WebServices.Data.Mailbox($mailboxaddress)
$FolderID = New-Object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$mailbox)
$FolderRoot = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($global:service,$FolderID);
$FolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(10000)
$FolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep
$findfolders = $FolderRoot.FindFolders($FolderView);
foreach ($folder in $findfolders.Folders){
$id = New-Object Microsoft.Exchange.WebServices.Data.FolderId($folder.Id.UniqueId.ToString())
$fld = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($global:service,$id);
$perms = $fld.Permissions
[int]$permcount = $fld.Permissions.Count
write-host $permcount
write-host $fld.Displayname, $fld.Permissions.Count
foreach($f in $fld.Permisions.UserID.PrimarySmtpAddress){
write-host $f
}
for($t=0;$t -le $perms.Count; $t++){
[string]$displayname = $fld.Permissions[$t].UserId.DisplayName
[string]$smtp = $fld.Permissions[$t].UserId.PrimarySmtpAddress
#write-host $mailboxaddress,$fld.DisplayName,$smtp
}
}
}
You need request that Exchange return the Folder Permissions with a PropertySet eg
$psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropset.Add([Microsoft.Exchange.WebServices.Data.FolderSchema]::Permissions)
$fld = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($global:service,$id,$psPropset);
And that should then return the permissions
Cheers
Glen

folders downloading as files in ftp

So in my powershell script when it starts up it polls a ftp server and downloads any files that aren't in the local folder. The problem is when it gets to a folders it downloads them as files. this is my code for checking for new files:
$LocFolder = 'C:\EMSDropBox\*'
Remove-Item $LocFolder
$ftprequest = [System.Net.FtpWebRequest]::Create("ftp://NZHQFTP1/tbarnes")
$ftprequest.Proxy = $null
$ftprequest.KeepAlive = $false
$ftprequest.TimeOut = 10000000
$ftprequest.UsePassive = $False
$ftprequest.Credentials = New-Object System.Net.NetworkCredential("tbarnes", "Static_flow2290")
$ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::ListDirectory
$FTPResponse = $ftprequest.GetResponse()
$ResponseStream = $FTPResponse.GetResponseStream()
$FTPReader = New-Object System.IO.Streamreader($ResponseStream)
$filename = $FTPReader.ReadLine()
while($filename -ne $null)
{
try
{
if((Test-Path ("C:\emsdropbox\"+$filename)) -ne $true)
{
downloadFtp($filename)
}
$filename = $FTPReader.ReadLine()
}
catch
{
Write-Host $_
}
}
$FTPReader.Close()
$FTPResponse.Close()
$ResponseStream.Close()
and this is the downloadFtp function:
# FTP Config
$FTPHost = "****"
$Username = "******"
$Password = "*********"
$FTPFile = $file
# FTP Log File Url
$FTPFileUrl = "ftp://" + $FTPHost + "/tbarnes/" + $FTPFile
# Create FTP Connection
$FTPRequest = [System.Net.FtpWebRequest]::Create("$FTPFileUrl")
$FTPRequest.Credentials = New-Object System.Net.NetworkCredential($Username, $Password)
$FTPRequest.Method = [System.Net.WebRequestMethods+Ftp]::DownloadFile
$FTPRequest.UsePassive = $false
$FTPRequest.UseBinary = $true
$FTPRequest.KeepAlive = $false
$targetfile = New-Object IO.FileStream (("C:\emsdropbox\"+$file),[IO.FileMode]::Create)
# Get FTP File
$FTPResponse = $FTPRequest.GetResponse()
$ResponseStream = $FTPResponse.GetResponseStream()
$FTPReader = New-Object -typename System.IO.StreamReader -ArgumentList $ResponseStream
[byte[]]$readbuffer = New-Object byte[] 1024
#loop through the download stream and send the data to the target file
do{
$readlength = $ResponseStream.Read($readbuffer,0,1024)
$targetfile.Write($readbuffer,0,$readlength)
}
while ($readlength -ne 0)
$FTPReader.Close()
Im not sure why it wont pull them down as folders so any help or pointers would be great!
The FTP methods don't support downloading of folders, or recursion, by themselves, so there's no other way I can think of doing this but what I've suggested below.
Change the method so you can differentiate between files and directories and handle them accordingly.
Change $ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::ListDirectory to $ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::ListDirectoryDetails
Which will list in this format:
-rwxrwxrwx 1 owner group 277632 Mar 4 17:15 xml_socket.log.rar
Directories will have a d in place of the - at the start of the line.
Here is an amended try block that will match only files so you can pass to the downloadFtp function:
try
{
if(!($filename -match '^d')){if((Test-Path ("C:\emsdropbox\"+$filename.Split(" ")[8])) -ne $true)
{
downloadFtp(($filename -split '\s+')[8])
}}
$filename = $FTPReader.ReadLine()
}
If you want to then get a list of directories, use the following try block against the same ftpresponse stream and for each, ListDirectoryDetails to get the list of files in each directory to process them:
try
{
if($filename -match '^d'){if((Test-Path ("C:\emsdropbox\"+$filename.Split(" ")[8])) -ne $true)
{
listdir(($filename -split '\s+')[8])
}}
$filename = $FTPReader.ReadLine()
}
You may also have to create the local directories too which you can do in powershell as follows:
New-Item c:\emsdropbox\newdir -type directory