Download attachments with multiple subjects from Exchange - powershell

I am attempting to download attachments from emails with certain subject lines in an inbox, then delete the email.
There are about a dozen different subject lines in total.
I want to schedule this process to run every 10 minutes or so, so I'd like to keep any processing overheads to a minimum..
Current script is in PowerShell (based on this), but I'm unsure how I could make it loop through a bunch of different subjects. I could just repeat the whole script for each subject, but it's quite inefficient this way..
I'm open to non-PowerShell alternatives too..
$MailboxName = "mailbox#address.com"
$Subject = #("Subject1")
$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\1.2\Microsoft.Exchange.WebServices.dll"
$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.company.com.au/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])
$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($Sfsub)
$sfCollection.add($Sfha)
$view = new-object Microsoft.Exchange.WebServices.Data.ItemView(2000)
$frFolderResult = $InboxFolder.FindItems($sfCollection,$view)
foreach ($miMailItems in $frFolderResult.Items){
$miMailItems.Subject
$miMailItems.Load()
foreach($attach in $miMailItems.Attachments){
$attach.Load()
$fiFile = new-object System.IO.FileStream(($downloadDirectory + “\” + (Get-Date -Format "yyMMdd") + "_" + $attach.Name.ToString()), [System.IO.FileMode]::Create)
$fiFile.Write($attach.Content, 0, $attach.Content.Length)
$fiFile.Close()
write-host "Downloaded Attachment : " + (($downloadDirectory + “\” + (Get-Date -Format "yyMMdd") + "_" + $attach.Name.ToString()))
}
$miMailItems.isread = $true
$miMailItems.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite)
$miMailItems.delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::MoveToDeletedItems)
}

Okay, here's what I've got:
$Subjects = #(
'newIM_IPSC',
'newCM_IPSC',
'CNSDI',
'SMEC_Incident_SLM',
'Incident_SLM',
'MEC_Incident_WorkInfo',
'SMEC_Incident_Header',
'SMEC_SR_Header'
)
[regex]$SubjectRegex = ‘^(?i)(‘ + (($Subjects |foreach {[regex]::escape($_)}) –join “|”) + ‘)$’
Then:
foreach($attach in $miMailItems.Attachments){
foreach ($miMailItems in $frFolderResult.Items){
if ($miMailItems.Subject -match $SubjectRegex)
{
$miMailItems.Load()
foreach($attach in $miMailItems.Attachments){
$attach.Load()
$fiFile = new-object System.IO.FileStream(($downloadDirectory + “\” + (Get-Date -Format "yyMMdd") + "_" + $attach.Name.ToString()), [System.IO.FileMode]::Create)
$fiFile.Write($attach.Content, 0, $attach.Content.Length)
$fiFile.Close()
write-host "Downloaded Attachment : " + (($downloadDirectory + “\” + (Get-Date -Format "yyMMdd") + "_" + $attach.Name.ToString()))
}
$miMailItems.isread = $true
$miMailItems.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite)
$miMailItems.delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::MoveToDeletedItems)
}
}
Add or remove subject lines from the $Subjects array as needed.
An explanation of the bits that are building the regex can be found here:
http://blogs.technet.com/b/heyscriptingguy/archive/2011/02/18/speed-up-array-comparisons-in-powershell-with-a-runtime-regex.aspx

Related

This command is not available. By writing to Word with Powershell

My powershell script yesterday stopped working. I have changed nothing in the code or in the path to files. It just stopped working. I can't explain it.
I've tested all path and all variables and its look fine.
function Gen-Doc($Replace, $dataToRaplace, $user_name, $austauch) {
$objWord = New-Object -comobject Word.Application
$objWord.Visible = $false
if ($austauch -eq $true) {
$objDoc = $objWord.Documents.Open("C:\Path\To\Docu\Vorlage\Doc1.docx")
} else {
$objDoc = $objWord.Documents.Open("C:\Path\To\\Docu\Vorlage\Doc2.docx")
}
$objSelection = $objWord.Selection
$n = 0
while ($n -ne $dataToRaplace.Length) {
$FindText = $Replace[$n]
$ReplaceWith = $dataToRaplace[$n]
$MatchCase = $False
$MatchWholeWord = $true
$MatchWildcards = $False
$MatchSoundsLike = $False
$MatchAllWordForms = $False
$Forward = $True
$Wrap = $wdFindContinue
$Format = $False
$wdFindContinue = 1
$wdReplaceAll = 2
$a = $objSelection.Find.Execute($FindText,$MatchCase,$MatchWholeWord, `
$MatchWildcards,$MatchSoundsLike,$MatchAllWordForms,$Forward,`
$Wrap,$Format,$ReplaceWith,$wdReplaceAll)
$n++
}
$filename = $user_name
$objDoc.SaveAs("C:\Path\To\Docu\$filename.docx")
$objWord.Quit()
if ($checkbox2.Checked -eq $true) {
Start-Process -FilePath "C:\Path\To\Docu\$filename.docx" -Verb print
}
}
And here is error message
This command is not available.
At C:\Scripts\Done\Ausgabe.ps1:103 char:9
+ $a = $objSelection.Find.Execute($FindText,$MatchCase,$MatchWh ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMException
Make sure you don't have your document opened before doing $objDoc.SaveAs.
Check process manager for orphan WINWORD.EXE processes running.

Create SharePoint list views for multiple libraries

I am creating views for multiple libraries in SharePoint. I am able to create view from below code successfully for one library. But I am stuck when I run the code in a loop. Below is my code
Code
Add-PSSnapin "Microsoft.SharePoint.Powershell" -EA SilentlyContinue
$subSiteURL = #("http://test.test.com/departments/Collections", "http://test.test.com/departments/Events")
$listName = #("Collections","Events")
for ($i=0; $i -lt $subSiteURL.length; $i++) {
$SPWeb = Get-SPWeb $subSiteURL[$i]
$ListName = $listName[$i]
$List = $SPWeb.Lists[$ListName]
$ViewTitle = "WebPart View"
$viewFields = New-Object System.Collections.Specialized.StringCollection
$viewFields.Add("DocIcon")
$viewFields.Add("LinkFilenameNoMenu")
$viewQuery = "<GroupBy Collapse='FALSE' GroupLimit='100'> <FieldRef Name='Category' Ascending='True'/> <FieldRef Name='Sub_x002d_Category' Ascending='True'/> </GroupBy> <OrderBy> <FieldRef Name='FileLeafRef' /></OrderBy> <Where><Or><Leq><FieldRef Name='Start_x0020_Date' /><Value Type='DateTime'>[Today]</Value></Leq><And><Geq><FieldRef Name='Expiry_x0020_Date' /><Value Type='DateTime'>[Today]</Value></Geq><Eq><FieldRef Name='Always_x0020_Display' /><Value Type='Boolean'>Yes</Value></Eq></And></Or></Where>"
$viewRowLimit = 999
$viewPaged = $false
$viewDefaultView = $false
$newView = $list.Views.Add($viewTitle, $viewFields, $viewQuery, $viewRowLimit, $viewPaged, $viewDefaultView)
$newView.TabularView = $False
$newView.Scope = "Recursive"
$newView.Update()
}
Error
You cannot call a method on a null-valued expression.
At D:\\CreateDocLibrariesViews.ps1:24 char:28
+ $newView = $list.Views.Add <<<< ($viewTitle, $viewFields, $viewQuery, $viewRowLimit, $viewPaged, $viewDefaultView)
+ CategoryInfo : InvalidOperation: (Add:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Try below script:
Add-PSSnapin "Microsoft.SharePoint.Powershell" -EA SilentlyContinue
$subSiteURL = #("http://sp", "http://sp:12001")
$listNames = #("MyDoc")
for ($i=0; $i -lt $subSiteURL.length; $i++) {
Write-Host $subSiteURL[$i]
$SPWeb = Get-SPWeb $subSiteURL[$i]
for ($j=0; $j -lt $listNames.length; $j++) {
$ListName = $listNames[$j]
Write-Host $ListName
$List = $SPWeb.Lists[$ListName]
$ViewTitle = "WebPart View"
$viewFields = New-Object System.Collections.Specialized.StringCollection
$viewFields.Add("DocIcon")
$viewFields.Add("LinkFilenameNoMenu")
$viewQuery = "<GroupBy Collapse='FALSE' GroupLimit='100'> <FieldRef Name='Category' Ascending='True'/></GroupBy> <OrderBy> <FieldRef Name='FileLeafRef' /></OrderBy>"
$viewRowLimit = 999
$viewPaged = $false
$viewDefaultView = $false
$newView = $list.Views.Add($viewTitle, $viewFields, $viewQuery, $viewRowLimit, $viewPaged, $viewDefaultView)
$newView.TabularView = $False
$newView.Scope = "Recursive"
$newView.Update()
}
}
Write-Host "done"

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)

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

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.

Need help figuring out why Powershell is throwing error 16

I posted this script the other day in an effort to discover a good way to change file extensions when "saving as." I had the problem licked, but as of this morning, the script will not run without errors. Here's the error message I'm getting:
Processing : C:\users\xxx\Desktop\ht\Automatic_Post-Call_Survey.htm
Exception calling "SaveAs" with "16" argument(s): "This is not a valid file name.
Try one or more of the following:
* Check the path to make sure it was typed correctly.
* Select a file from the list of files and folders."
At C:\users\xxx\Desktop\hd.ps1:11 char:20
+ $opendoc.saveas <<<< ([ref]"$docpath\$doc.FullName.doc", [ref]$saveFormat);
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
if "16" is the error code, that represents an inability to delete the directory...but it doesn't appear as if I'm asking for that at all--unless there's some default parameter in place somewhere. I'm pretty much baffled.anyone have any other ideas I can try out?
$docpath = "c:\users\xxx\desktop\do"
$htmPath = "c:\users\xxx\desktop\ht"
$srcfiles = Get-ChildItem $htmPath -filter "*.htm*"
$saveFormat = [Enum]::Parse([Microsoft.Office.Interop.Word.WdSaveFormat], "wdFormatDocument");
$word = new-object -comobject word.application
$word.Visible = $False
$filename = ($_.fullname).substring(0,($_.FullName).lastindexOf("."))
function saveas-document {
$opendoc = $word.documents.open($doc.FullName);
$opendoc.saveas([ref]"$docpath\$filename", [ref]$saveFormat);
$opendoc.close();
}
ForEach ($doc in $srcfiles) {
Write-Host "Processing :" $doc.FullName
saveas-document
$doc = $null
}
$word.quit();
this should do what do you need, but is not the best design :)
$docpath = "c:\users\xxx\desktop\do"
$htmPath = "c:\users\xxx\desktop\ht"
$srcfiles = Get-ChildItem $htmPath -filter "*.htm*"
$saveFormat = [Enum]::Parse([Microsoft.Office.Interop.Word.WdSaveFormat], "wdFormatDocument");
$global:word = new-object -comobject word.application
$word.Visible = $False
#$filename = ($_.fullname).substring(0,($_.FullName).lastindexOf("."))
function saveas-document ($docs) {
$opendoc = $word.documents.open($docs);
$savepath = $docs -replace [regex]::escape($htmPath),"$docpath"
$savepath = $savepath -replace '\.html*', '.doc'
$opendoc.saveas([ref]"$savepath", [ref]$saveFormat);
$opendoc.close();
}
ForEach ($doc in $srcfiles) {
Write-Host "Processing :" $doc.FullName
saveas-document -doc $doc.FullName
$doc = $null
}
$word.quit();