Currently, I'm trying to get attachment names from E-Mails within a PST-File. I want to do this via Powershell. My code bracket works fine so far, except for one thing. It Just writes System.__ComObject as the attachments name. Any ideas?
## Path where the PSTFiles are
$PSTArr = Get-ChildItem -Path .\ -Recurse
$Attachments = #()
## Processing
ForEach ($PST in $PSTArr) {
if ($PST.Name -like "*.pst") {
[string]$pstPath = $PST.FullName
Write-Output ("Checking File: " + $pstPath)
# Lets see if there is a running outlook process
$oProc = ( Get-Process | where { $_.Name -eq "OUTLOOK" } )
if ( $oProc -eq $null ) { Start-Process outlook -WindowStyle Hidden; Start-Sleep -Seconds 5 }
$outlook = New-Object -ComObject Outlook.Application
# Assign namespace
$namespace = $outlook.GetNamespace("MAPI")
# Add PST
$namespace.AddStore($pstPath)
$pstStore = ( $nameSpace.Stores | where { $_.FilePath -eq $pstPath } )
# Add RootFolder of the PST
$pstRootFolder = $pstStore.GetRootFolder()
# Get attachments of the E-Mails
$Attachments += $pstRootFolder.Items | Select Attachment
# Disconnect PST File
$namespace.GetType().InvokeMember('RemoveStore',[System.Reflection.BindingFlags]::InvokeMethod,$null,$namespace,($pstRootFolder))
}
}
(I'm looping through a Folder with PST's, which works fine)
Thanks for help / answers
Try this:
$Attachments = $pstRootFolder.Items | ForEach-Object {
$_.Attachments | Select-Object -ExpandProperty FileName
}
Related
I need to search for a word in a row from a spreadsheet and update another cell in the same row with a different value. For example, I have the data like this. I need to search for the person "Smith" from the below spreadsheet and update the value of the 'Status' column from 'Enabled' to 'Disabled' for that row.
"Region","Zone","Customer","Process","Status"
"TEST","East","Smith","HR","Disabled"
"TEST","East","Allen","Finance","Enabled"
"TEST","East","Jake","Payroll","Enabled"
I tried regex and few other functions before posting the question. But I can't get them to work.
Thanks.
It's very easy to use Excel with PowerShell:
Add-Type -AssemblyName Microsoft.Office.Interop.Excel
$excelFile = 'C:\test\testsheet.xlsx'
$searchFor = 'Smith'
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $true
$excel.ScreenUpdating = $true
$workbook = $excel.Workbooks.Open( $excelFile ,$null, $false )
$ws = $workbook.WorkSheets.item(1)
[void]$ws.Activate()
$searchRange = $ws.UsedRange
$searchResult = $searchRange.Find( $searchFor, [System.Type]::Missing, [System.Type]::Missing,
[Microsoft.Office.Interop.Excel.XlLookAt]::xlWhole,
[Microsoft.Office.Interop.Excel.XlSearchOrder]::xlByColumns,
[Microsoft.Office.Interop.Excel.XlSearchDirection]::xlNext )
while( $searchResult ) {
$row = $searchResult.Row
$col = $searchResult.Column
$ws.Cells( $row, $col + 2 ).Value2 = 'Disabled'
$searchResult = $searchRange.FindNext( $searchResult )
if( $searchResult -and $searchResult.Row -le $row ) {
break
}
}
[void]$workbook.Save()
[void]$workbook.Close()
[void]$excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null
I got it working using the below script.
$TGTSERVER = "testservwc01"
$name = "ORATDLLSTR"
$input = Invoke-Command -ComputerName "$TGTSERVER" -ScriptBlock {Import-Csv 'C:\test.csv'}
$value = "Disabled"
$Output = foreach ($i in $input) {
if ($i.Process_Instance -match "$name") {$i.Status = "$value"} $i }
$OutArray = $Output | Select-Object -Property * -ExcludeProperty PSComputerName, RunspaceId, PSShowComputerName
$OutArray | Invoke-Command -ComputerName "$TGTSERVER" -ScriptBlock {Export-Csv 'C:\test.csv' -NoTypeInformation}
if ( $LastExitCode -ge 1)
{
Write-Warning -Message "$Computer : Disable Step failed"
exit 1
}
However the script fails with exit code 1 even though it updates the csv file with the right value on the remote server.
I have found solution which meet my needs... using Powershell
Not the issue mentioned as in topic.. but overall module have a lot of options which might help modify Excel File using PowerShell
https://www.powershellgallery.com/packages/PSWriteExcel/0.1.15
https://github.com/EvotecIT/PSWriteExcel/blob/master/Examples/Run-Example-FindReplace.ps1
Install-Module -Name PSWriteExcel
Import-Module PSWriteExcel -Force
$FilePath = "D:\Excel_test.xlsx"
$FilePathOutput = "D:\Excel_test1.xlsx"
Find-ExcelDocumentText -FilePath $FilePath -Find 'evotec' -Replace -ReplaceWith 'somethingelse' -FilePathTarget $FilePathOutput -OpenWorkBook -Regex -Suppress $true
I have a powershell script which downloads outlook mail attachments.
But I recieve below error:
Method invocation failed because [System.String] does not contain a method named 'saveasfile'.
I have used below script
Add-Type -Assembly "Microsoft.Office.Interop.Outlook"
$Outlook = New-Object -ComObject Outlook.Application
$Namespace = $Outlook.GetNameSpace("MAPI")
$inbox = $mapi.GetDefaultFolder(6)
$saveFilePath = "C:\temp\"
$subfolder = $inbox.Folders | Where-Object {$_.Name -eq “REPORTS”}
$mail = $subfolder.Items | Select-Object -Property Subject,SentOn,#{name="Attachments";expression={$_.Attachments|%{$_.DisplayName}}} | Where-Object{$_.attachments -match ".html" -and ($_.SentOn -gt '29-Oct-19 12:00:00 AM')}
foreach ($email in $mail)
{
if ($email.attachments.count -ge 1)
{
foreach ($attachment in $email.attachments)
{
$filename = $attachment.filename
$attachment.saveasfile((join-path $savefilepath $filename))
}
}
}
Please let me know how can I resolve this error.
Outlook's Attachments property's enumerator returns a String key, not an actual attachment object (this is why using an untyped or weakly-typed language like PowerShell for working with COM is a very bad idea).
While PowerShell wants to be used as a functional, pipelining language (which is a PITA to debug) - sometimes it's easier to use old-fashioned imperative code:
For( $i = 0; $i -le $email.Attachments.Count; $i++ ) {
$attachment = $email.Attachments.Item( $i )
$attachment.SaveAsFile( ( Join-Path $savefilepath $filename ) )
}
BTW, you don't need the if ($email.attachments.count -ge 1) statement because the For( $i = 0; $i... loop also checks the .Attachments.Count property too.
I have a script that is supposed to download all attachments from "Inbox" then move them to another folder named "Processed". I filter mails by subject. If exist then download all attachments and move the mails.
For example I have 5 mails with attachments (xls) the subject are named like:
BB_asd_green_dsa
AD_acv_blue_sf
FD__c_d_red_sa
CV_saas_white_sdsd
as_dc_asa_purple
When I start the script it saves 3 attachments, the mails with attachments are moved too. And then it stops.
Second start. It saves 1 attachment & moves 1 mail
And one last manual start script saves the last attachment and moves the mail.
For what reason doesn't the script save and move all attachments with their mails?
Here is the script:
$olFolderInbox = 6
$outlook = New-Object -Com Outlook.Application;
$ns = $outlook.GetNameSpace("MAPI");
#$ns.Logon("Outlook")
#$ns.SendAndReceive($true)
#Start-Sleep -s 0
$inbox = $ns.GetDefaultFolder($olFolderInbox)
$messages = $inbox.Items
Write-Host $messages.Count
foreach ($message in $messages) {
$msubject = $message.Subject
$b = $msubject.Filename
$filepath = "D:\Users\h.yordanov\Desktop\convertexltohtm\xls\"
$message.Attachments | foreach{
Write-Host $message.Filename
$a = $_.Filename
$msubject = $message.Subject
$msubjectW = $msubject -replace '[^0-9]', ''
$b = $msubjectW + ".xls"
if ($msubject.Contains("green") -or $msubject.Contains("blue") -or $msubject.Contains("green") -or $msubject.Contains("white") -or $msubject.Contains("purple")) {
$_.SaveAsFile((Join-Path $filepath $b))
}
}
$MoveTarget = $inbox.Folders.Item("Processed")
[void]$message.Move($MoveTarget)
}
I used this script to move the mails, but still not all 5 mails are moved at once:
$Date = [DateTime]::Now.AddDays(-180)
$Last6Months = $Date.tostring("MM/dd/yyyy")
$o = New-Object -comobject outlook.application
$n = $o.GetNamespace(“MAPI”)
$Account = $n.Folders | ? { $_.Name -eq 'h.yordanov#sample.com' };
$Inbox = $Account.Folders | ? { $_.Name -match 'Inbox' };
$TargetFolder = $Inbox.Folders.Item('Processed')
$Inbox.Items |
Where-Object -FilterScript {
$_.senton -ge "$Last6Months"
} |
ForEach-Object -Process {
[void]$psitem.Move($TargetFolder)
}
Btw i am using MS office Pro plus 2010 64-bit
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
}
}
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.