Powershell - How to filter an attachment before download - powershell

I am using a PowerShell script to download attachment from an email which has multiple attachment.
If I use below statement it will download all the attachments.
# Find Unread mail messages
$UnreadMessages = $Inbox.Items | where-object {$_.Unread -and $_.SenderEmailAddress -imatch "usa"}
I want to download only a specific attachment using below statement but it gives nothing.
# Find Unread mail messages
$UnreadMessages = $Inbox.Items | where-object {$_.Unread -and $_.SenderEmailAddress -imatch "usa" -and $_.Attachments -imatch "Backlog"}
Please help me correct this statement

Firstly, your code will cause all Inbox messages to be downloaded and processed by your script. This is like a SELECT statement in SQL without a WHERE clause - as bad as it gets performance wise.
Use Items.Find/FindNext or Items.Restrict (see https://learn.microsoft.com/en-us/office/vba/api/outlook.items.find) - let the server/message store do the work. For your first query, use
#SQL=("urn:schemas:httpmail:read" = 0) AND ("http://schemas.microsoft.com/mapi/proptag/0x0065001F" like '%usa%')
For the second query, OOM won't let you search on the attachment name even though Extended MAPI (C++ or Delphi only) exposes that functionality (create RES_SUBRESTRICTION on PR_MESSAGE_ATTACHMENTS and specify PR_ATTACH_LONG_FILENAME as the search property). You can of course use only your first query and loop over the query matches, for each entry looping through each Attachment object in the MailItem.Attachments collection - far from ideal, but still better than no restriction at all.
If using Redemption (I am its author - it is an Extended MAPI wrapper and can be used from any language) is an option, it allows to use Attachments in queries. Something like the following (off the top of my head, VBA):
set Session = CreateObject("Redemption.RDOSession")
Session.MAPIOBJECT = Application.Session.MAPIOBJECT
set Folder = Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox)
set restrItems = Folder.Items.Restrict(" (UnRead = 'true') AND (""http://schemas.microsoft.com/mapi/proptag/0x0065001F"" like '%usa%') AND (Attachments LIKE '%Backlog%')")
for each item in restrItems
Debug.Print item.Subject
next

Related

Is there any way to export the contents of my Teams Chats for one day, by chat and time?

Is there any way to get and list out all my conversations in teams chats for one day? When I'm logging time, I lose track of what I worked on when I'm helping others, and the find function just gives me 8 messages on a big screen, which means I have to scroll through dozens of screens to get the "oh, this is what we're doing". I'd rather have a list with the chat name, time, and what was said. Powershell, CLI, KQL, don't care how. Thanks.
John Doe Chat: Doe, John [10:35] what is this about
John Doe Chat: Me, Myself [10:36] trying to come up with an example
PeopleChatOne: Joe [10:37] what are we, chopped liver?
Okay, after digging for a bit... turns out the answer is, as #sayali-msft and #anothervictimofthemouse mentioned, is the Graph API - which has a module. Note that you DO have a certain amount of access to your own stuff without needing an admins help, but you will have to explicitly grant it to each "application" that will connect to it. And there's a good amount of playing you can do with the Graph Explorer.
The below code will grab all of your chats, then grab the most recent 200 messages, filtered for the past day, and then will return the pertinent info.
Still missing at this point:
time zones seem wonky, specifically people in other time zones.
piping to OGV gives you all lines, when I really only want the first.
Fortunately, format-table DOES limit to one line per message
stripping out HTML works but is not great.
members may not be working.
Install-Module Microsoft.Graph
Import-Module Microsoft.Graph.Teams
$RequiredScopes = #("Chat.ReadBasic", "Chat.ReadWrite")
Connect-MgGraph -Scopes $RequiredScopes
#at this point a browser window should appear - allow it.
#I had to find my user id in graph explorer UI by running GET V1.0 https://graph.microsoft.com/v1.0/me
#unsure how to get it otherwise - but you don't need it with get-mgchat
get-mgchat
#take one of those IDs
#let's look at this chat:
get-mgchat -ChatId 19:deadbeefb2d949d88d4455f5279e5d8b#thread.v2
get-mgchatmessage -ChatId 19:deadbeefb2d949d88d4455f5279e5d8b#thread.v2
#this nests and walks through properly, strips HTML, but lord this will be slow. And shows more than one line per message, even when I try now to.
#By default you can get all your chats by running get-mgchat. -all and -pagesize 50 is required for the module to paginate the request and get you everything. But in my case it grabbed all 2000 chats. The -first 5 is for testing.
$tzone = Get-TimeZone # conversion from GMT to local time. https://jdhitsolutions.com/blog/powershell/7962/convert-to-local-time-with-powershell/
$mychats = get-mgchat -all -PageSize 50 |select -first 5
$all_chat_info = #() #force-setting to an array
$all_chat_info = foreach ($chat in $mychats) {
$chatinfo = get-mgchat -ChatId $chat.id #get base details about the CHAT itself
#set some details about the chat itself for the later query
$chatname = $chat.Topic
$members = $chat.Members
$chattype = $chat.chattype
#now get every message from that chat since midnight yesterday. Note LastModifiedDateTime is GMT. The jdhit page says -($tzone...), but all I had to do was .tolocaltime() ... I think.
#the -top 200 -pagesize 50 is to get the most recent 200 messages, and again you have to paginate.
$recentchatmessages = get-mgchatmessage -ChatId $chat.id -top 200 -pagesize 50 |where {$_.LastModifiedDateTime.tolocaltime() -gt (get-date).date.AddDays(-1)} # all from after midnight yesterday |select -first 5
#and now use select expression to add the above fields and parse the below fields, stripping out HTML (but I can't seem to only get the first line in OGV)
$recentchatmessages | select #{Label='LastModified';Expression={($_.LastModifiedDateTime.tolocaltime())}}, #{Label='ChatName';Expression={($chatname)}}, #{Label='members';Expression={($members)}}, #{Label='ChatType';Expression={($chattype)}},
#{Label='From';Expression={($_.from.user.displayname)}}, #{Label='Body';Expression={ ($_.Body.content -split '\n')[0] -replace '<[^>]+>',''}}
##{Label='From';Expression={($_.from.user.displayname)}}, #{Label='Body';Expression={( ($_.Body.content -replace '<[^>]+>','').split([Environment]::NewLine)|select -first 1)}}
}
$all_chat_info|format-table
#and now close/disconnect
Disconnect-MgGraph
Several problems present themselves:
All and PageSize appear broken (update: use "-all -pagesize 50")
Expanding the fields. Probably trivial for others, but a pain for me.
currently comes back like
Microsoft.Graph.PowerShell.Models.MicrosoftGraphItemBody
Microsoft.Graph.PowerShell.Models.MicrosoftGraphChatMessageFromIdentitySet
Some references I found while getting it working.
https://practical365.com/connect-microsoft-graph-powershell-sdk/ - getting the scope set
graph explorer
https://www.powershellgallery.com/packages/Microsoft.Graph/1.9.2
https://github.com/microsoftgraph/msgraph-sdk-powershell - the github for the actual modules. the readme is gold specifically https://github.com/microsoftgraph/msgraph-sdk-powershell/blob/dev/samples/5-Teams.ps1

Exchange online - move messages with powershell

I have a huge mailbox (~50Gb) with plenty of messages in 'Inbox' . I'm looking for moving certain messages received before particular date to another folder at the same mailbox.
I have tried to do it from Outlook Windows app but it seems too slow and I can't do it for all the messages at once. Outlook just crash.
Is there any way to perform the task from Exchange Powershell ? I can certainly create server side rule but how to apply it to the messages already in 'Inbox' ?
New-InboxRule -Name Testmove2018 -Mailbox test -MoveToFolder "MailboxName:\2018" -ReceivedAfterDate "09/01/2015"
Oleg
The Exchange Online powershell module still allows you to copy and delete messages matching a search with Search-Mailbox, but you must copy them elsewhere first. Use an empty mailbox:
# First, check your search filter using EstimateOnly or LogOnly:
Search-Mailbox myUser#domain.com -SearchQuery 'Subject:Move Me' -EstimateResultOnly
# Copy the items to a temp mailbox and delete from the primary
# (EXO does not allow copying search results to same mailbox)
# param block just for readability:
$Params = #{
Identity = 'myUser#domain.com'
SearchQuery = 'Subject:Move Me'
TargetMailbox = 'temp#domain.com'
TargetFolder = 'Temp'
DeleteContent = $true
}
$TempResult = Search-Mailbox #Params
# Now move back to original mailbox
$Params.Identity = 'temp#domain.com'
$Params.TargetMailbox = 'myUser#domain.com'
$Params.TargetFolder = 'Moved'
$MoveResult = Search-Mailbox #Params
Then just make sure the number of emails is equal. EXO can take a while to get synced up, so if it's not finding all the email to move back, just give it a while and run the second search again:
If ($TempResult.ResultItemsCount -eq $MoveResult.ResultItemsCount) {
"We're good!"
} Else {
Write-Warning "Move results not equal!"
$TempResult
$MoveResult
}
Note that this is the 'Old' method, which is already retired and may be removed in the future. The new method is supposed to be using the New-ComplianceSearch style commands, but they have some limitations and aren't built for this purpose.
In my opinion, the "easiest" way is to open the mailbox in the browser > search for what you want > scroll down to load all the messages if needed > select all > move to > "move to a different folder..."

Search emails by subject using powershell

All,
I'm stuck at the following. I get list of emails in my inbox, and I need to search emails that contain specific string in subject (and then parse body of that email). I'm stuck at getting right syntax for filtering emails by subject.
I have this:
$Outlook = New-Object -comObject Outlook.Application
$namespace = $Outlook.GetNameSpace("MAPI")
$inbox = $namespace.GetDefaultFolder([Microsoft.Office.Interop.Outlook.OlDefaultFolders]::olFolderInbox)
$emails = $inbox.items
$subjectComparisonExpression = "Meeting topic is: "
But then none of these work:
#1
$inbox.items | Where-Object {$_.Subject -like $subjectComparisonExpression} | Write-Host($_.Subject)
#2
$myemails = $inbox.items | Where-Object {$_.Subject -like $subjectComparisonExpression}
Write-Host($myemails.count)
#3
$myemails = $emails | Where {$_.Subject -like $subjectComparisonExpression}
Write-Host($myemails.count)
How do I get list of emails where subject contains $subjectComparisonExpression?
Use the Find/FindNext or Restrict methods of the Items class to find items that correspond to your conditions. The Restrict method is an alternative to using the Find method or FindNext method to iterate over specific items within a collection. The Find or FindNext methods are faster than filtering if there are a small number of items. The Restrict method is significantly faster if there is a large number of items in the collection, especially if only a few items in a large collection are expected to be found. You can read more about them in the following articles:
How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
How To: Use Restrict method to retrieve Outlook mail items from a folder
Problem was with '-like' keyword. Replaced it with '-match' and it works.

O365 Powershell | Breaking up a long list into two sets of 100

I am looking to create a rule in Office 365 applied to all of the members in our org.
I would like this rule to append a warning on all incoming email from outside the organization with the same Display Names as our users.
When I attempt to apply it to all of the users in our org I get an error stating that the rule is too long.
In order to solve that I pulled a group, but I am still about 1000 characters over the limit.
I would like to make two variables, that each hold one half of the list, created by this command:
(Get-DistibutionGroupMember -Identity email#contoso.com -ResultSize Unlimited).DisplayName
I have attempted to modify the ResultSize parameter, but what I would need is result 1-100 and then 100-200 from the same list.
Another caveat to this problem is that the list cannot be static. It is something that the script will have to update every time it is run.
There is a sub-string command that you can use on a particular username that I have utilized when I made something for AD, but I am not aware of any way to break up a list like this.
If anyone has any other ways to solve this issue I would be more than open to any suggestion.
Thanks for taking the time to read this!
There are many ways of doing it. I found it very readable.
My favorite one is this one:
$ObjectList = 1..1000
$Step = 100
$counter = [pscustomobject] #{ Value = 0 }
$ObjectListSplitted = $ObjectList | Group-Object -Property { math]::Floor($counter.Value++ / $step) }
Then if you want to show the third subset just use this format :
$ObjectListSplitted[3].Group
Have a look to this solution already explained.
As a note other languages are capable of slicing an array of object with a start, stop and a step, have a look here if you're curious.

Powershell: Checking for duplicate email in AD

Background:
I'm trying to make a script that will see if a new users email ($email) is the same as one already existing (which would cause an error). I have a very remedial understanding of objects so this is what I have so far (yes it is ugly):
$email = "smithj#company.com"
$mailcheck = Get-ADUser -filter * -Properties * | ForEach-Object {$_.mail}
$mailcheck | ForEach-Object {if ($email -eq $_.mail){"$email = $($_.mail) - Matching email"}else{"$email = $($_.mail) - No duplicate email"}}
Problem 1:
The script doesn't match emails. When I have a matching email in AD it doesn't recognize it.
Problem 2: When executing just the 2nd line, indexing doesn't work properly. While it looks like a consecutive list of emails, if a user doesn't have an email at all (blank) really it could be something like this:
smithj#company.com
johnsonj#company.com
robertsr#company.com
doej#company.com
So $mailcheck[0] returns smithj#company.com while $mailcheck[1] returns blank despite the list actually looking like this:
smithj#company.com
johnsonj#company.com
robertsr#company.com
doej#company.com
Conclusion: I really just need problem 1 solved but problem 2 peaked my curiosity. Thanks.
The way you are doing it above is really inefficient. -Properties * will return every property on the user, some properties are expensive in terms of processing power to return. Only use the properties you need. The properties returned by default without specifying that parameters do not need to be specified with -Properties, only additional nondefault properties. -Filter * will also match on literally any value for any field, effectively returning every ADUser, further increasing the resources required for your script to execute as you will now have to process every user to find any accounts matching that email.
Now that that's out of the way, here is a more efficient method to implement what you're asking:
# Set the email address to search for
$emailAddress = 'box#domain.tld'
# Get all users where the email address matches what is set above
# Force it as an array so you can treat it like one even if only
# one or zero users are returned
$adUsers = #( Get-ADUser -Filter "EmailAddress -eq '${emailAddress}'" )
# Make sure no accounts were returned
# If there are, throw an error with the number of users and who they are
if( $adUsers ) {
throw "Found $($adUsers.Count) users matching EmailAddress ${emailAddress}: $($adUsers.SamAccountName -join ', ')"
}
By using the filter to only match the specific email address, Powershell does not need to collect every single AD user in the system, or iterate over all of them to find a specific email address. This will take a long time to check, especially in larger environments, whereas filtering the returned objects based on email address (or on any other property) results in a faster operation and less data to sift through.
You can then check whether $adUsers contains anything (an array count of anything but 0 evaluates to $True, you could also use if( $adUsers.Count -gt 0 ) as the condition), and if so, throw an error with more information as I do above.
Update for comment question:
To answer your other question in the comment, "I didn't know what object to compare $email to", EmailAddress and Mail both look to be valid properties, but I don't know the difference between them. In my environment, both Mail and EmailAddress are populated with my email address, but have always used EmailAddress and haven't run into issues using that. Maybe one is deprecated and the other is new or something, but I'm not really sure.
There is also yet another property called proxyAddresses as well, which preliminary research shows that both EmailAddress and Mail are related to it, but I don't know much about it. It's not populated on my ADUser objects, so I can't poke around with it.