<< A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

–A–

AlternateEmailAddresses, users that have at least one

Get-MsolUser -all | where {($_.AlternateEmailAddresses.count -gt 0)} | ft UserPrincipalName,AlternateEmailAddresses

arrays embedded in results, deal with - see audit log, search for an example using JSON

audit log, search

This example finds all new email rule creations on a certain day

$auditEventsForUser = Search-UnifiedAuditLog -StartDate '2018-12-14' -EndDate '2018-12-15' -UserIds [someUser] -RecordType ExchangeAdmin -Operations New-InboxRule
$ConvertedOutput = $auditEventsForUser | Select-Object -ExpandProperty AuditData | ConvertFrom-Json

$ConvertedOutput | Select-Object CreationTime,Operation,Workload,ClientIP,Parameters | ft -Wrap -a

audit mailboxes, enable

unlike SharePoint, there doesn't seem to be any way to globally enable auditing for all mailboxes. Instead, must do it one mailbox at a time. And if you add a new user, you have to make sure to do it for him as well. Here's at least a command to enable auditing on all mailboxes:

Get-Mailbox -ResultSize Unlimited -Filter {RecipientTypeDetails -eq "UserMailbox" } | Set-Mailbox -AuditEnabled $true

automap a shared mailbox, remove for several users

You can't just remove automapping. Instead, you must remove full access rights and then add full access again but this time with the automapping set to $false instead of default $true

specify the shared mailbox:

$sharedMailbox = "wantToShare@yourDomain.com"

verify the existing permissions on this shared mailbox. I normally use this in order to get rid of inherited permissions:

Get-Mailbox $sharedMailbox | Get-MailboxPermission | where {$_.user.tostring() -ne "NT AUTHORITY\SELF" -and $_.IsInherited -eq $false} | Select-Object user, AccessRights

But if you run the simpler version, you might see a "SID" tucked in there somewhere:

Get-Mailbox $sharedMailbox | Get-MailboxPermission

In at least one instance, I did get a SID and it was inherited. This may cause a problem.

populate array with delegates who you're going to remove and then add back permissions (but without automapping):

$delegates = @("user1@yourDomain.com", "user2@yourDomain.com", "user3@yourDomain.com")
$delegates | %{Remove-MailboxPermission -Identity $_ -User $sharedMailbox -AccessRights FullAccess -Confirm:$false}

I sometimes get the following message:

WARNING: Can't remove the access control entry on the object "CN=your user,OU=yourTenant.onmicrosoft.com,OU=Microsoft Exchange Hosted Organizations,DC=NAMPR10A004,DC=PROD,DC=OUTLOOK,DC=COM" for account "NAMPR10A004\share53598-1921233685" because the ACE doesn't exist on the object.

which doesn't make sense. But I proceed anyway.

$delegates | %{Add-MailboxPermission -Identity $_ -User $sharedMailbox  -AccessRights FullAccess -AutoMapping:$false}

verify:

$delegates | %{Get-Mailbox | Get-MailboxPermission -user $_}

I have yet to find any PowerShell command that actually tells the status of automapping. The command above merely verifies that the individuals still are delegates to the target shared mailbox with full access.

–B–

Behalf - as in "SendOnBehalfTo" - see

delegated mailboxes that a user has SendOnBehalfTo, SendOnBehalfTo, add this permission for a user on a shared mailbox

–C–

calendar activity, examine log

Get-CalendarDiagnosticLog -Identity someUser -StartDate "10/3/2018 6:00:00 AM" -EndDate "10/3/2018 5:00:00 PM" | ogv

I use this to test whether adding calendar items is reflected in another user who may have been inadvertently added as a delegate at the Outlook (not Exchange) level.

calendar, allow someone else to view - see also public folders, add permission

Add-MailboxFolderPermission whoseCalYouWantRead@yourDomain.com:\Calendar -User whoNeedsAccess@yourDomain.com -AccessRights FolderVisible,ReadItems

If the person already has one permission - say AvailabilityOnly, you might get an error.

An existing permission entry was found for user: Who NeedsAccess

So, use this instead:

Set-MailboxFolderPermission -Identity whoseCalYouWantRead@yourDomain.com:\Calendar -User whoNeedsAccess@yourDomain.com -AccessRights FolderVisible,ReadItems

Curiously, after I run the command above, when I look to see what permission

Get-MailboxFolderPermission -Identity whoseCalWantToKnowPerms@yourDomain.com:\Calendar

It shows the user as now having Reviewer permissions. I would have expected an array of the two separate permissions we actually tried to add: FolderVisible,ReadItems

To grant one user access to most of the folks in his own OU (optionally filtering out anyone whose name starts with someString):

Get-ADUser -SearchBase "OU=yourOU,DC=yourDomain,DC=com" -Filter * -SearchScope Subtree | ? {($_.Name -notlike "someString*") -and ($null -ne $_.UserPrincipalName) -and ( $_.UserPrincipalName -notlike "userWhoNeedsAccess*")} | select UserPrincipalName | % {Add-MailboxFolderPermission "$($_.UserPrincipalName):\Calendar" -User userWhoNeedsAccess@yourDomain.com -AccessRights FolderVisible,ReadItems}

This gives errors for folks who don't have email boxes or whom already grant permission to this user but otherwise works OK.

calendar, display all calendars for a user

Get-MailboxFolderStatistics "someUser" | ? {($_.foldertype -eq "Calendar") -or ($_.folderpath -like "/Calendar*")} | ft Name, Identity, folderpath, foldertype

calendar, remove permissions

Remove-MailboxFolderPermission -Identity whoseCalendar@yourDomain.com:\Calendar -User userWhoWantsPerms -confirm:$false

calendar, who has what permissions?

Get-MailboxFolderPermission -Identity whoseCalWantToKnowPerms@yourDomain.com:\Calendar

conference room, create - see room, create

conference rooms, filter out from list of mailboxes- see mailbox types, filter out types

contact, add - New-MailContact. Curiously, even though there's a Get-MsolContact command, there does not appear to be any corresponding New-MsolContact command

replicate all contacts in an OU in local AD to cloud (useful if not syncing)

$contacts = Get-ADObject -filter {objectclass -eq "contact"} -SearchBase "OU=yourOU,DC=yourDomain,DC=com" -Properties name, givenName, middleName, sn, mail | `
    Sort-Object sn, givenName | select name, givenName, middleName, sn, mail
$contacts | % {New-MailContact -Name $_.Name -DisplayName $_.Name -ExternalEmailAddress $_.mail -FirstName $_.FirstName -LastName $_.LastName}

replicate all users in an OU in local AD to cloud

$users = Get-ADUser -filter * -SearchBase "OU=somOU,DC=yourDomain,DC=com" `

    -Properties company, department, displayName, givenName, mail, middleName, name, physicalDeliveryOfficeName, sn, telephoneNumber, targetAddress

$users | % {New-MailContact -DisplayName $_.DisplayName -Name $_.Name -FirstName $_.givenName `
    -ExternalEmailAddress $_.targetAddress.split(":")[1] -LastName $_.LastName}

There are many attributes you simply can't include when adding a contact. These include such important attributes as: company, department, office, phone, title. But we can add them after the fact using the Set-Contact command.

$users | % {Set-Contact $_.DisplayName -company $_.company -department $_.department -Office $_.physicalDeliveryOfficeName -phone $_.telephoneNumber -title $_.title}

contact, find and remove a contact with the same name as a user

Find

$DepartingUserIdentity = "someUser";

Get-ADObject -LDAPFilter "objectClass=Contact" -Properties Name,mail,DistinguishedName  | Where-Object{$_.mail -like "$($DepartingUserIdentity)*"} | ft Name, mail, DistinguishedName

Remove

$EmployeeDetails = Get-ADUser $DepartingUserIdentity -properties *

Get-ADObject -Filter {(cn -eq $EmployeeDetails.Name) -and (objectClass -eq "Contact")} | Remove-ADObject -Confirm:$False

or hard-coded name instead of using a variable:

Get-ADObject -Filter {(cn -eq "Some User") -and (objectClass -eq "Contact")} | Remove-ADObject -Confirm:$False

contact info (with proxyAddress),

Get-MailContact -Identity someUser @someDomain.net | select DisplayName,alias,externalEmailAddress,emailAddresses

contacts, bad according to DirSync - find and remove

run this:

Get-MsolContact -All | where {($_.ValidationStatus -eq "Error")} | sort DisplayName | select DisplayName, EmailAddress, objectID

in order to get known problems. To remove same:

Get-MsolContact -All | where {($_.ValidationStatus -eq "Error")} | Remove-MsolContact -Force

This doesn't always get everything. So, try this to find more:

Get-MsolDirSyncProvisioningError -ErrorCategory PropertyConflict | where {$_.ObjectType -EQ "contact"} | ft

And then delete these:

Get-MsolDirSyncProvisioningError -ErrorCategory PropertyConflict | where {$_.ObjectType -EQ "contact"} | Remove-MsolContact -Force

contacts, display "proxyAddresses" and "targetAddress"

contacts on the cloud don't really have the same "proxyAddresses" or "targetAddress" as local AD contacts. Instead, they have "externalEmailAddress" and "emailAddresses" analogs:

Get-MailContact | select DisplayName,alias, externalEmailAddress, emailAddresses

contacts, proxyAddresses add

this example takes whatever the main address is and adds another email with a new suffix:

$contacts = Get-MailContact | Select-Object Identity, PrimarySmtpAddress, emailAddresses
$contacts | % {Set-MailContact -Identity $_.Identity -EmailAddresses @{Add = "smtp:" + $_.PrimarySmtpAddress.split("@")[0 ] + "@yourDomain.com"}}

–D–

delegate a mailbox to another user - see permissions - assign mailbox permissions/delegation of one user to another user

delegated mailbox does not show up as expected in Outlook

You've delegated full access rights on a mailbox to another user's mailbox. And the delegated user can see the mailbox and emails for that user just fine in WebMail. But the delegated user cannot see the other user's mailbox in Outlook. Or if the shared mailbox itself is visible in the user's mailbox, there are no emails there in that mailbox. Answer: you have to take away full access and then add it back again with Automapping. The following forces all of a user's delegates to show up in Outlook.

$Delegates = Get-Mailbox | Get-MailboxPermission -user 'someUser@yourDomain.com'
$Delegates | %{Remove-MailboxPermission -Identity $_.Identity -user $_.User -AccessRights FullAccess -Confirm:$False}
$Delegates | %{Add-MailboxPermission -Identity $_.Identity -user $_.User -AccessRights FullAccess -AutoMapping:$True}

Perhaps, instead, you want to make sure just one of a user's delegates to show up in his Outlook. If, for example, you want to reset the "marketing" shared mailbox for "Bob", "marketing" is the "sourceUser" below and "Bob" is the "targetUser" below:

Get-Mailbox "sourceUser@yourDomain.com" | Remove-MailboxPermission -user "targetUser@yourDomain.com" -AccessRights FullAccess -Confirm:$False | Add-MailboxPermission -AccessRights FullAccess -Automapping $true -User "targetUser@yourDomain.com"

If you want to verify, you can run something like this:

Get-Mailbox "gdpr@foodchainid.com" | Get-MailboxPermission | where {$_.user.tostring() -ne "NT AUTHORITY\SELF" -and $_.IsInherited -eq $false}

Unfortunately, it doesn't return any automapping field. That column doesn't show up even if you add a "| fl" at the end. I haven't yet figured out how to get the automapping field to show up in any PowerShell command.

delegates for a (normally shared) mailbox

Get-Mailbox someUser | Get-MailboxPermission | where {$_.user.tostring() -ne "NT AUTHORITY\SELF" -and $_.IsInherited -eq $false} | Select-Object user, AccessRights

remove one of the delegates

Remove-MailboxPermission -identity someUser -user someDelegate@yourDomain.com -AccessRights FullAccess -confirm: $false

delegated mailboxes that a user has access to

Get-Mailbox | Get-MailboxPermission -user 'someUser@yourDomain.com'

delegated mailboxes that a user has FullAccess

find

$DepartingUserIdentity = "someUser";

Get-Mailbox | Get-MailboxPermission -user $DepartingUser

(or another way)

Get-Mailbox | ? {$_.GrantSendOnBehalfTo -match $DepartingUserIdentity}

Or, with an individual, hard-coded email address:

Get-Mailbox | Get-MailboxPermission -user 'someUser@yourDomain.com'

(or another way)

Get-Mailbox | ? {$_.GrantSendOnBehalfTo -match "someUser"}

The most common permission we need to worry about is “FullAccess”.

Remove

Attempt to remove “FullAccess” in one fell swoop fails because we run out of threads

Get-Mailbox | Get-MailboxPermission -user $DepartingUser | % {Remove-MailboxPermission -identity $_.Identity -user $_.User -AccessRights FullAccess -InheritanceType All -confirm: $false}

Error is

Remove-MailboxPermission : The session WinRM1, 24b1bbc8-5f00-4836-b7c0-097b589ed891, outlook.office365.com is not available to run commands.  The session availability is Busy.

Which means trying to do too much at once.

But split this up into 2 parts, seems to work better

$targetUsers = Get-Mailbox | Get-MailboxPermission -user $DepartingUser

$targetUsers | % {Remove-MailboxPermission -identity $_.Identity -user $_.User -AccessRights FullAccess -InheritanceType All -confirm: $false}

delegated mailboxes that a user has SendOnBehalfTo

find

$DepartingUserIdentity = "someUser";

Get-Mailbox | ? {$_.GrantSendOnBehalfTo -match $DepartingUserIdentity}

remove

delegates, generate list

Get-Mailbox | Get-MailboxPermission | where {$_.user.tostring() -ne "NT AUTHORITY\SELF" -and $_.IsInherited -eq $false}

delegate, remove - see also remove shared (delegated) mailboxes for a user

Remove-MailboxPermission -identity someUser -user someDelegate@yourDomain.com -AccessRights FullAccess -confirm: $false

what if you get:

The operation couldn't be performed because 'someUser' matches multiple entries.

Get IDs for the dupes:

Get-Mailbox someUser | select UserprincipalName, Guid, ExchangeGuid

then re-run the delete command using the Guid:

Remove-MailboxPermission -identity c5909e04-1407-423f-82c8-0f60de50cf0c -user someDelegate@yourDomain.com -AccessRights FullAccess -confirm: $false

You may ask: why not just rename one of the aliases to remove the alias? First of all, if you sync with local AD, you'd have to make the change in local AD. Second of all, there's probably a good reason why there are duplicate aliases. "Alias" is the same as "proxyAddress". And you might one user with someuser@yourDomain1.com and another someuser@yourDomain2.com. Like if this user moved to a different division but we kept the old mailbox hanging around as a shared mailbox for successor to monitor.

delete emails

This will delete all emails for a particular user which are older than a certain date:

Search-Mailbox -Identity "Bob Smith" -SearchQuery "(Received < 11/1/2018)" -DeleteContent -Confirm:$false

or

Get-Mailbox -Identity "some body" | Search-Mailbox -SearchQuery "(Received < 11/1/2018)" -DeleteContent

A few things to note:

I've found that if I run this too many times in succession, I might get an error:

Deletion failed with error 'Move/Copy messages failed.' for the following item:

And then followed by the subject and some other gobbledegook for each of hundreds of emails in the date range. I think this has to do with the index getting corrupt. If I wait a day or so, it seems I can resume. I had this problem deleting 10,000 at a time for a mailbox which had over half a million records. Even if I crunched down the time span to a much smaller interval:

Search-Mailbox -Identity "some body" -SearchQuery {Received:9/1/2018..9/2/2018} -DeleteContent -Confirm:$false

which, in this case, resulted in only 3 records, I still get the same error

deleted emails, permanently delete

Search-Mailbox -Identity someUser -SearchQuery '#deleted items#' -DeleteContent

Should return something like:

WARNING: The Search-Mailbox cmdlet returns up to 10000 results per mailbox if a search query is specified. To return more than 10000 results, use the New-MailboxSearch cmdlet or the In-Place eDiscovery & Hold console in the Exchange Administration Center.
Confirm
Deleting content from mailboxes someUser
[Y] Yes [A] Yes to All [N] No [L] No to All [?] Help (default is "Yes"): a
 

RunspaceId       : 1e74c7bf-a89d-44d8-b9bc-3298b5f5ab93
Identity         : Some User
TargetMailbox    :
Success          : True
TargetFolder     :
ResultItemsCount : 68
ResultItemsSize  : 16.2 MB (16,987,063 bytes)

To get

deleted emails, recover

My initial mistake of setting "Discovery Search Mailbox" as the target mailbox was the beginning of a long detour.

Search-Mailbox "haplessEndUser@theirDomain.com" -SearchQuery "subject:""$9 million dollar signed contract!""" -TargetMailbox "Discovery Search Mailbox" -TargetFolder "Signed Contract Recovery" -LogLevel Full

This worked.  I got 8 items:

RunspaceId       : d8758080-fe7c-4006-ae91-3a0d56aad4e0
Identity         : Hapless EndUser
TargetMailbox    : DiscoverySearchMailbox{D919BA05-46A6-415f-80AD-7E09334BB852}
Success          : True<
TargetFolder     : \Signed Contract Recovery\Hapless EndUser-3/22/2018 3:48:33 PM
ResultItemsCount : 8
ResultItemsSize  : 245.4 KB (251,260 bytes)

But I should have thought better before sending this to the “Discovery Search Mailbox".  Where the heck is that?  It turns out the system somehow recognized the word "Discovery Search" and made a mailbox with special, limited properties. Those properties include locking any search results in it in such a way that once they go in, you can't move them anywhere else. But I didn't know that right off. So I tried moving these same 8 items somewhere else.  Note: you can’t just send it to the same mailbox as the source mailbox or you’ll get this:

WARNING: The source mailbox 'Hapless EndUser' will not be searched because it is the target mailbox. The source mailbox cannot be used as the target mailbox.

So, instead send to my email box:

Search-Mailbox "haplessEndUser@theirDomain.com" -SearchQuery "subject:""$9 million dollar signed contract!""" -TargetMailbox "longSufferingSysAdmin@myDomain.com" -TargetFolder "Signed Contract Recovery" -LogLevel Full

But that returns nothing:

RunspaceId       : d8758080-fe7c-4006-ae91-3a0d56aad4e0
Identity         : Hapless EndUser
TargetMailbox    : Longsuffering SysAdmin
Success          : True
TargetFolder     : \Signed Contract Recovery\Hapless EndUser-3/22/2018 4:05:25 PM
ResultItemsCount : 0
ResultItemsSize  : 0 B (0 bytes)

Well, that was annoying and surprising.  Let’s see if we can dig them out of that discovery mailbox and put them in my mailbox:

Search-Mailbox "Discovery Search Mailbox" -SearchQuery "subject:""$9 million dollar signed contract!""" -TargetMailbox "longSufferingSysAdmin@myDomain.com" -TargetFolder "Signed Contract Recovery" -LogLevel Full

Again, no results:

RunspaceId       : d8758080-fe7c-4006-ae91-3a0d56aad4e0
Identity         : DiscoverySearchMailbox{D919BA05-46A6-415f-80AD-7E09334BB852}
TargetMailbox    : Longsuffering SysAdmin
Success          : True
TargetFolder     : \Signed Contract Recovery\Discovery Search Mailbox-3/22/2018 4:06:57 PM
ResultItemsCount : 0
ResultItemsSize  : 0 B (0 bytes)

Same “no results” if I choose a more verbose name I find for this box:

Search-Mailbox "DiscoverySearchMailbox{D919BA05-46A6-415f-80AD-7E09334BB852}" -SearchQuery "subject:""$9 million dollar signed contract!""" -TargetMailbox "longSufferingSysAdmin@myDomain.com" -TargetFolder "Signed Contract Recovery" -LogLevel Full

OK, so they’re stuck in this weird Discovery box.  Can I make myself a delegate?

Get-Mailbox -Resultsize unlimited -Filter {RecipientTypeDetails -eq "DiscoveryMailbox"} | `
    Add-MailboxPermission -AccessRights FullAccess -Automapping $true -User "longSufferingSysAdmin@myDomain.com"

Apparently:

WARNING: The appropriate access control entry is already present on the object "CN=DiscoverySearchMailbox{D919BA05-46A6-415f-80AD-7E09334BB852},OU=yourDomain.onmicrosoft.com,OU=Microsoft Exchange Hosted Organizations,DC=NAMPR10A004,DC=PROD,DC=OUTLOOK,DC=COM" for account "NAMPR10A004\jmosc50247-845198855".

 

Identity             User                 AccessRights   IsInherited Deny
--------             ----                 ------------   ----------- ----
DiscoverySearchMa... NAMPR10A004\jmosc... {FullAccess}

Well, it didn’t error out.  But it seems to think I already have access.  This didn’t show up as a new mailbox in Outlook.  Nor coulc I find it through WebMail. But after half an hour or so, it finally did show up as a delegated box in my Outlook and I was able to copy the email to my main mailbox & send (couldn’t send from that mailbox because I hadn’t set myself up with “SendAs” privileges.)

So, the big lesson learned: just send it to my own dang email box to begin with.  Otherwise it gets trapped in that Discovery mailbox that’s created on the fly and the only way to get it out then is by setting myself up as a delegate.

deleted mailboxes, list - see also deleted users (soft deleted), list

Get-Mailbox -SoftDeletedMailbox | Select DisplayName,ExchangeGuid,PrimarySmtpAddress,ArchiveStatus,DistinguishedName | Out-GridView -Title "Select Mailbox and GUID" -PassThru

deleted mailbox, recover mailbox when synced user associated with that deleted mailbox is still present

First, get the ExchangeGUID of the deleted mailbox

get-mailbox -SoftDeletedMailbox -identity somedeleteduser | fl ExchangeGUID

Simlarly, get the ExchangeGUID of the target user. Make sure this target user has an email licenses of some kind and that we've logged in at least once - to create an empty mailbox to migrate into

get-mailbox -identity userWhoLostHisMailbox | fl ExchangeGUID

Copy the stuff from the deleted mailbox to the target, using the ExchangeGUIDs you got above as appropriate:

new-MailboxRestoreRequest -SourceMailbox "8c86592c-5cb7-4bc5-8b06-7f6a57b84d2b" -TargetMailbox "4c587005-e303-4689-aed7-564e49b0734b" -AllowLegacyDNMismatch

deleted mailbox, recover a soft deleted / disconnected mailbox merged to another user on exchange online - So You Need To Recover A Soft Deleted / Disconnected Mailbox Merged To Another User On Exchange Onlines or Office 365 and Exchange Online Restore and Recover Processes for Soft-Deleted Mailboxes

DirSync errors, list

Get-MsolDirSyncProvisioningError -ErrorCategory PropertyConflict | ft

disabled accounts, filter out from list of mailboxes - see mailbox types, filter out types

distribution group, bulk change WindowsEmailAddress of cloud-only (exclude those synced with local AD)

$distGroup = Get-DistributionGroup | ? {$_.isdirsynced -eq 0 -and ($_.WindowsEmailAddress.split("@")[1] -match "yourdomain.com")}

Optional: inspect first before proceding to the command that actually applying our changes:

$distGroup | ft name, proxyAddresses

Now proceed to actually do what we set out to do: set "PrimarySmtpAddress" for all users which had corresponding "PrimarySmtpAddress" correpsonding to our domain:

$distgGp | %{Set-DistributionGroup -identity $_.identity -WindowsEmailAddress ($_.WindowsEmailAddress.split("@")[0] +"@yourTenant.onmicrosoft.com")}

Note that we could have done all this in one command without the intermediate variable. But it's nice to actually see the group we intend to change things before we actually apply changes (using the Set-DistributionGroup command) just to make sure.

distribution group, guest ID incorporation into - see guest ID, delegate of shared mailbox

The short story is: you must make sure your guest IDs have their WindowsEmail attribute populated for them to work properly.

domain, list all emails for

Get-MsolUser | where {($_.userprincipalname -match "yourDomain.com")}

or

Get-Mailbox *yourDomain.com

list shared mailboxes for a domain with who has permissions on them

Get-Mailbox *yourDomain.com | Get-MailboxPermission | where {$_.user.tostring() -ne "NT AUTHORITY\SELF" -and $_.IsInherited -eq $false} | Select-Object Identity, User, AccessRights

domains, sort email boxes by - see mailboxes, sort by domain

–E–

email traffic

Best place I've found to get this is directly from the message trace log. I've had pretty good luck with Get-DetailedMessageStats.ps1. It neatly summarizes traffic by email box per day with separate columns for inbound & outbound counts & sizes. The only weird things were:

  1. Even though I specified credentials as an argument, it still insisted on collecting them from me
  2. Although it claims to attempt to grab all the data available in the log (which I would think would be around 30 days), It only got 3 days' worth

There's other traffic besides sent/received emails - at least as measured by our gateway. Such traffic that comes to mind is Outlook fetching email for non-cached clients. Haven't figured out how to capture that from the server yet.

emails, find all emails with some subject in some user's email box - see subject of email, find

–F–

folders, list

We want to list a user's folders in order of biggest to smallest. We should be able to do it like this:

Get-MailboxFolderStatistics someUser | ? {$_.ItemsInFolderAndSubfolders -gt 0} | sort FolderAndSubfoldersize -Descending | Select Name,FolderandSubFolderSize,ItemsinFolderandSubfolders

But if you're in a remote session (as you likely would be if you're trying to run this on a Microsoft tenant), this will instead sort the FolderAndSubfoldersize field as if it were a text field instead of a number. You can't get an un-serialized object from a remote session. The data stream between the sessions is SOAP. This is HTTP/HTTPS, so it has to be serialized to a text stream. If you were in a local session, it would probably work. Anyway, you'll get the FolderandSubFolderSize data in a format that looks something like this:

940.7 KB (963,235 bytes)

We need to extract the stuff in parentheses, get rid of the commas, and remove the " bytes". The code snippet below achieves this by creating a new custom "Size" field which is numeric instead of character and therefore can be sorted properly:

@{name="Size"; Expression = {$tmp = [regex]::match($_.FolderandSubFolderSize, "\((.*)\)").Groups[1]; [uint64]$foldersize = $tmp -replace ',','' -replace ' bytes',''; $foldersize }}

The code above is actually 3 separate lines separated by semicolons (";") run in succession, each line feeding into the next.

1. The first section extracts stuff in between the parentheses and puts that result in the $tmp variable.

$tmp = [regex]::match($_.FolderandSubFolderSize, "\((.*)\)").Groups[1]

2. Then get rid of the commas and the ' bytes' from the $tmp variable and convert it to a number using uint64 and put that in the intermediate $foldersize variable.

[uint64]$foldersize = $tmp -replace ',','' -replace ' bytes',''

3. And then finally simply spit out the value of the $foldersize variable.

$foldersize

Here's a version that uses the code above to achieve our main goal of sorting by size (our newly created "Size" field), biggest first (Sort-Object -Property Size -Descending):

Get-MailboxFolderStatistics someUser -IncludeOldestAndNewestItems | ? {$_.ItemsInFolderAndSubfolders -gt 0 } | Select-Object @{name="Location";Expression={$_.Identity -replace 'someUser\\',''}},FolderandSubFolderSize,ItemsinFolderandSubfolders, @{name="Size"; Expression = {$tmp = [regex]::match($_.FolderandSubFolderSize, "\((.*)\)").Groups[1]; [uint64]$foldersize = $tmp -replace ',','' -replace ' bytes',''; $foldersize }}, NewestItemReceivedDate, OldestItemReceivedDate | Sort-Object -Property Size -Descending | ConvertTo-HTML -Property Location,FolderandSubFolderSize,ItemsinFolderandSubfolders, NewestItemReceivedDate, OldestItemReceivedDate | Out-File -FilePath "$([environment]::GetFolderPath("mydocuments"))\someUserFolders$((Get-Date).ToString('MM-dd-yyyy_hh-mm-ss')).htm"

This does a bunch of other stuff:

FullAccess delegated, to which users does a particular user have this access? - see delegated mailboxes that a user has FullAccess

–G–

GAL - see Global Address List (GAL) (or Offline Address Book / OAB), suppress entries

allow guest IDs to show up in the GAL - see Guest ID, show in GAL

Get-MailboxStatistics

individual

Get-MailboxStatistics -Identity "someUser@yourTenant.com"

By default, this command also gives the LastLogonTime but it does not give what we care most about: how big the dang box actually is. So make sure that shows up in the output, too.

Get-MailboxStatistics -Identity "someUser@yourTenant.com" | ft DisplayName, ItemCount, TotalItemSize

for a domain

Get-Mailbox *yourDomain.com | Select -Expand UserPrincipalName | Get-MailboxStatistics | Select DisplayName, @{name="TotalItemSize (MB)"; expression={[math]::Round(($_.TotalItemSize.ToString().Split("(")[1].Split(" ")[0].Replace(",","")/1MB),2)}}, ItemCount | Export-Csv -Path "$([environment]::GetFolderPath("mydocuments"))\fileBaseName$((Get-Date).ToString('MM-dd-yyyy_hh-mm-ss')).csv"

If you don't include the Select -Expand UserPrincipalName above, you might get an error related to duplicate names:

The specified mailbox "Some User" isn't unique.

for the whole tenant

$allMailboxes = Get-Mailbox | Select -Expand UserPrincipalName | Get-MailboxStatistics | Select DisplayName, @{name="TotalItemSize (MB)"; expression={[math]::Round(($_.TotalItemSize.ToString().Split("(")[1].Split(" ")[0].Replace(",","")/1MB),2)}}, ItemCount | export-csv "MailboxSizes.csv"

followed by this to see the biggest ones at the top:

$allMailboxes | select DisplayName, "TotalItemSize (MB)", ItemCount | sort "TotalItemSize (MB)" -Descending

Global Address List (GAL) (or Offline Address Book / OAB), suppress entries - the key is either:


Cloud only (not local AD)

This first section assumes that you want to change this attribute for cloud-only IDs that are not synced with local AD.

To list mailboxes showing the status of this attribute

Get-Mailbox | Sort-Object HiddenFromAddressListsEnabled,displayName | ft identity,displayName,HiddenFromAddressListsEnabled

list individuals whose status is false or null

Get-Mailbox | ? {($_.HiddenFromAddressListsEnabled -eq $true) -or ($_ .HiddenFromAddressListsEnabled -eq $null) } | ft identity,displayName,HiddenFromAddressListsEnabled

how to change this attribute for an individual

If a user has an email license, the following two commands will work to find …

Get-Mailbox -Identity someuser@yourTenant.onmicrosoft.com | ft identity,displayName,HiddenFromAddressListsEnabled

…and remove them from showing up in the GAL or OAB

Set-Mailbox -Identity someuser@yourTenant.onmicrosoft.com -HiddenFromAddressListsEnabled $true

 

Bulk method #1: Set-MailUser

But what if these users don't have an email license but have that annoying "HiddenFromAddressListsEnabled" set to "$false" (the default)? They'll still show up in the GAL and you can't get at them using the Get-Mailbox command as we do above! This comes up if we had a user that was synced with local AD and an email license, deleted him, and then restored him and take away his email license. We do this, for instance, if we move him from one tenant to another but decide to let him hang around in some capacity with no email license but perhaps a SharePoint license. Assume that we only care about "real" emails and not emails ending with "*.onmicrosoft.com". This command all by itself finds them:

Get-MailUser | Where {($_.UserPrincipalName -like '*onmicrosoft.com') -and ($_.HiddenFromAddressListsEnabled -eq $False)

And then this following command goes one step further to gets rid of the offending "HiddenFromAddressListsEnabled" by setting it to "true".

Get-MailUser | Where {($_.UserPrincipalName -like '*onmicrosoft.com') -and ($_.HiddenFromAddressListsEnabled -eq $False)} | ForEach-Object {Set-MailUser $_.userprincipalname -HiddenFromAddressListsEnabled $true}

 

Bulk method #2: Set-Mailbox

Sometimes, even though these users don't have a license, using the Set-Mailbox command instead of Set-MailUser works anyway:

$onmicrosoftUsersNotHidden = Get-Mailbox *onmicrosoft.com -filter {HiddenFromAddressListsEnabled -eq $False}

Note that, unlike other commands, for Get-Mailbox it seems that using Where in a pipe after the initial command won't filter properly. Instead, you must apply the filter immediately after the Get-Mailbox with a simple wildcard - "*onmicrosoft.com" in this case

Optional: make sure we have the right users before actually applying our changes:

$onmicrosoftUsersNotHidden | ft userPrincipalName,displayName,HiddenFromAddressListsEnabled

Now proceed to actually do what we set out to do: hide these users from showing up in the GAL

$onmicrosoftUsersNotHidden | % {Set-Mailbox -identity -identity $_.identity -HiddenFromAddressListsEnabled $true}


local AD users (not cloud-only)

$DepartingUserIdentity = "someUser";

Set-ADUser -identity $DepartingUserIdentity -add @{msExchHideFromAddressLists = $True}

Or maybe "replace" instead of "add" if the value is not null (haven't tested)

change for guests - see Guest ID, show in GAL

guest ID, delegate of shared mailbox

Let‘s say we want 2 folks from our outside vendor added to the debug email shared box.  Turns out, there‘s a way to add them to that shared mailbox.  But because they‘re not inside the organization, there‘s no way they‘ll ever be able to see any such emails.  That is, as far as I know, you must be a licensed user inside the tenant in order to be able to see any emails to which you are a delegate and guest users are, by definition, outside the tenant.  So, going down that path is an exercise in futility!

The only way I can get guest users to be able to see emails sent to the shared mailbox is to forward everything sent to that shared box to a distribution list.  I had to do the following things:

Add the WindowsEmail attribute to the guest ID.  By default, guest IDs you add via the Azure AD interface don‘t have the WindowsEmail attribute filled in.  To list all our guest IDs and whether or not they have their WindowsEmail attribute filled in, run this:

Get-User -RecipientTypeDetails GuestMailUser | Get-MailUser | Select-Object DisplayName, name, UserPrincipalName, HiddenFromAddressListsEnabled, WindowsEmailAddress | Sort-Object DisplayName | ogv

This also shows whether or not they will show up in the GAL (HiddenFromAddressListsEnabled = True), but that‘s not especially important if all you want to do is make sure they can receive an email.  Then, to fill in the WindowsEmailAddress attribute:

Get-User | ? {$_.Name -eq "bobSmith_someCompany.com#EXT#"} | Set-MailUser -WindowsEmailAddress "bobSmith@someCompany.com" -HiddenFromAddressListsEnabled $false

Again, that part about setting HiddenFromAddressListsEnabled = True is optional.

Now that you‘ve set WindowsEmail attributes for these users, create a new cloud-only distribution group that includes those guest IDs. Note: I don't think anything from here on out really needs to be done via PowerShell; you should be able to do all of this through the regular O365 GUI you use to create groups.

New-DistributionGroup -Name "someCompany Debug" -Members bobSmith@some-Company.com,samSnead@some-company.com,someUser@yourDomain.com

Notice this command

all in one fell swoop.  Later I added another outside user

Add-DistributionGroupMember -Identity "someCompany Debug" -Member "someUser@someForeignTenant.onmicrosoft.com"

As stated above, you can also add such outside distribution group members through the GUI (not the Exchange GUI but the regular group management GUI in O365).

Once you're done with all this, then simply forward everything in this shared mailbox to the new distribution group you created. I've only ever done this through the GUI.

guest ID, show in GAL

set

Get-User -RecipientTypeDetails GuestMailUser | ? {$_.Name -eq "someExternalUser_someDomain.com#EXT#"} | Set-MailUser -HiddenFromAddressListsEnabled $false

the guest user will now show up in the GAL, but you still won't be able to mail to him. If you look for his email in the GAL, it will be blank? Why? The whole process of getting an external guest user set up involves sending him an email and having him use his email credentials to log in. His email sure ought to be in there somewhere. And, indeed, it is. But not in the right place yet. That's because, even though he has emails plugged into all sorts of properties, you need to add his email to one very particular property: WindowsEmailAddress

Get-User -RecipientTypeDetails GuestMailUser | ? {$_.Name -eq "someExternalUser_someDomain#EXT#"} | Set-MailUser -WindowsEmailAddress "someExternalUser@someDomain.com"

So, you'll want to do both:

Get-User -RecipientTypeDetails GuestMailUser | ? {$_.Name -eq "someExternalUser_someDomain#EXT#"} | Set-MailUser -WindowsEmailAddress "someExternalUser@someDomain.com" -HiddenFromAddressListsEnabled $false

verify

Get-MailUser "someExternalUser_someDomain.com#EXT#" | FL Name, WindowsEmailAddress, HiddenFromAddressListsEnabled

GUID for a mailbox

Get-Mailbox -identity someuser | select DisplayName, GUID, ExchangeGUID

–H–

HiddenFromAddressListsEnabled - this cloud attribute is equivalent to msExchHideFromAddressLists in local AD

What it's set to:

Get-Mailbox "someUser@yourDomain.com" | Select-Object displayName, HiddenFromAddressListsEnabled

To change it:

Get-Mailbox "someUser@yourDomain.com" | Set-Mailbox -HiddenFromAddressListsEnabled $true

change for guests - see Guest ID, show in GAL

–I–

–J–

JSON, deal with - see audit log, search for an example

–K–

kiosk mailbox, filter out from list of mailboxes - see mailbox types, filter out types

–L–

language zone bulk change

set all mailboxes in our Office 365 Tenant to German language

Get-mailbox -ResultSize unlimited | Set-MailboxRegionalConfiguration -Language 1031 -TimeZone "W. Europe Standard Time" -LocalizeDefaultFolderName

as well as set time zone to W. Europe and change all of the default folders

last mailbox login time - see Get-MailboxStatistics

Get-MailboxStatistics -Identity "someUser@yourDomain.com" | ft DisplayName, LastLogonTime

list mailboxes, see mailboxes, list

login time, most recent for mailbox - Get-MailboxStatistics

Get-MailboxStatistics -Identity "someUser@yourDomain.com" | ft DisplayName, LastLogonTime

–M–

mailbox, create

$UPN = "noreply@yourDomain.de"
$LO = "DE"
New-Mailbox -Alias noreply -Name noreply -Firstname noreply -LastName noreply -DisplayName "noreply" -MicrosoftOnlineServicesID $UPN -Password ( ConvertTo-SecureString -String 'topSecret' -AsPlainText -Force) -ResetPasswordOnNextLogon $true

Need to assign a location before we can assign a license. But we have to wait a bit after creation before we can set a location. So wait a few seconds.

start-sleep -s 20
set-msoluser -userprincipalname $UPN -UsageLocation $LO

Now assign a license. But we have to wait a bit after setting location before we can assign a license. So wait a few seconds.

start-sleep -s 20
Set-MsolUserLicense -UserPrincipalName $UPN -AddLicenses "yourTenant:EXCHANGESTANDARD_DE"

mailbox types, filter out types

filter out:

Get-Mailbox -ErrorAction SilentlyContinue -identity $_.UserPrincipalName `
    -Filter {(-not(RecipientTypeDetailsValue -eq 'SharedMailbox')) -and (-not(RecipientTypeDetailsValue -eq 'RoomMailbox')) `
    -and (-not(RecipientTypeDetailsValue -eq 'EquipmentMailbox')) -and (ExchangeUserAccountControl -ne 'AccountDisabled') `
    -and (HiddenFromAddressListsEnabled -eq $false) -and (MailboxPlan -notlike "ExchangeOnlineDeskless*")} | `
    Sort-Object MailboxPlan, Identity | `
    Select-Object DisplayName, WindowsEmailAddress, MailboxPlan | Export-Csv "EmailList.csv"

I could not filter out Kiosks. When I try to filter, you always get 0 records. But you can sort. So, sort, include that value in the display & lop off bottom records.

mailbox size - see Get-MailboxStatistics

mailboxes, list

This also sorts by domain

Get-Mailbox | Select-Object @{n="Dom";e={$_.UserPrincipalName.split("@")[1]}}, displayName, userprincipalname | Sort-Object dom, displayName

mailboxes, sort by domain

Get-Mailbox | Select-Object @{n="Dom";e={$_.UserPrincipalName.split("@")[1]}}, displayName, UserPrincipalName | Sort-Object dom, displayName

mailboxes which are deleted, list - see deleted mailboxes, list

message trace - see trace message

most recent mailbox login time - see Get-MailboxStatistics

Get-MailboxStatistics -Identity "someUser@yourDomain.com" | ft DisplayName, LastLogonTime

msExchHideFromAddressLists - this local AD attribute is equivalent to HiddenFromAddressListsEnabled in the cloud

–N–

–O–

Off-line Address Book (OAB), suppress entries from - see Global Address List (GAL), suppress entries

out of office message, see status

Get-Mailbox someUser@yourDomain.com | Get-MailboxAutoReplyConfiguration | select AutoReplyState, ExternalMessage, InternalMessage | fl

out of office message, specify

Initialize some variables

$message = "Hi. Thank you for your email. Bob Smith has left the company.
Please update your records with our accounts email address: accounts@yourDomain.com "
$EmployeeDetails = Get-ADUser Bob.Smith -properties *

Run the command

Set-MailboxAutoReplyConfiguration -Identity $EmployeeDetails.Mail -AutoReplyState enabled -ExternalAudience all -InternalMessage $message -ExternalMessage $message

Verify results

Get-MailboxAutoReplyConfiguration -Identity $EmployeeDetails.Mail

out of office message, turn off

Set-MailboxAutoReplyConfiguration -Identity someUser@yourDomain.com -AutoReplyState disabled

I sometimes like to go a step further and also clear out messages

Set-MailboxAutoReplyConfiguration -Identity someUser@yourDomain.com -AutoReplyState disabled -InternalMessage "" -ExternalMessage ""

Outlook doesn't display shared mailbox. Or if the mailbox itself does show the shared mailbox, there are no emails there in the mailbox. See delegates don't show up as expected in Outlook

–p–

permissions, assign mailbox permissions/delegation of one user to another user

The command below will give the user (perhaps a sysadmin) access to all mailboxes. The “Automapping $false” means that, even though the user will have permissions/be a delegate, the other peoples' mailboxes will not automatically show up in his Outlook

Get-Mailbox -ResultSize Unlimited | Add-MailboxPermission -AccessRights FullAccess -Automapping $false -User someuser@yourdomain.com

To give just one delegated user access to one source user (and also make sure that the other person's mailbox will automatically show up in his Outlook):

Get-Mailbox "sourceUser@yourDomain.com" | Add-MailboxPermission -AccessRights FullAccess -Automapping $true -User "targetUser@yourDomain.com"

or simpler:

Add-MailboxPermission -Identity sourceUser -AccessRights FullAccess -Automapping $true -User "targetUser@yourDomain.com"

Unlike the full access delegation above, you can't add "SendAs" permission with UserPrincipalName. Instead, you have to apply the "SendAs" permission using Identity.

$DepartingUserIdentity = "sourceUser";
$DelegatedUserIdentity = "delegatedUser";
Add-RecipientPermission $DepartingUserIdentity -AccessRights SendAs -Trustee $DelegatedUserIdentity -Confirm:$False

PowerShell, disable remote - see RemotePowerShellEnabled, disable

preparing a mailbox for this user

After assigning the licenses to the user in office 365, the user is stuck with the message, "We are preparing a mailbox for this user". Verify:

Get-MsolUser -UserPrincipalName someUser@yourDomain.com | ft DisplayName, UserPrincipalName, OverallProvisioningStatus

or

(Get-MsolUser -UserPrincipalName someUser@yourDomain.cim).Licenses[0].ServiceStatus

If either of the commands above return "OverallProvisioningStatus" of "PendingInput", the "PendingInput" status means that nothing has been provisioned (yet) for this customer. If this doesn't clear up soon, you may have a problem. According to here:

  1. Remove the existing licenses to the Office 365 User in office 365 portal or power shell and Wait for 24 hours to De-provision the resources in the target system.
  2. Re-Assign the Licenses to the user in Office 365 portal or Power Shell.
  3. After assigning the licenses to the user, the issue will be resolved.

proxyAddresses, add or delete

user

$OldToDelete = "SMTP:" + $identity + "@" + $TenantDomain
$NewToAdd = "smtp:" + $identity + "@" + $TenantDomain
Set-Mailbox -Identity $identity -EmailAddresses @{Add = $NewToAdd; remove = $OldToDelete}

can not change proxyAddresses using Set-MsolUser

add proxyAddresses for contacts - see contacts, proxyAddresses add

proxyAddresses, find match

Get-MsolUser -all | where-Object {$_.ProxyAddresses -match "someaddress" } | fl

proxyAddresses for contacts - although local AD contacts have "proxyAddresses", on Office 365 this property translates to "emailAddresses" - see contacts, display proxyAddresses and targetAddress

proxyAddresses, list for a contact - see contact info (with proxyAddress),

proxyAddresses, list for a user

Get-MsolUser -UserPrincipalName someUser@yourDomain.com | Select-Object DisplayName, UserPrincipalName, proxyAddresses

or

Get-Mailbox someUser | fl EmailAddresses

also show the primary proxyAddress

Get-MSOLUser -UserPrincipalName someUser@yourDomain.com | Select userprincipalname, @{e={$_.ProxyAddresses -cmatch '^SMTP\:.*'};name='Primaryaddress'},Proxyaddresses

public folders, add permission

Add-PublicFolderClientPermission -Identity "\Calendars\someCalendar" -User someUser -AccessRights PublishingEditor

public folders, list

Get-PublicFolder -resultsize unlimited -recurse

–Q–

–R–

remotePowerShellEnabled, disable

Disable for one user:

Set-User -Identity someUser@yourDomain.com -RemotePowerShellEnabled $false

Show all who are enabled

Get-User -ResultSize unlimited -Filter {RemotePowerShellEnabled -eq $true} | Sort Name | ft -Auto Name,DisplayName,RemotePowerShellEnabled

Often I want to do this for a whole OU. Although OU (Organizational Unit) is ostensibly available as an option in the Get-User and Set-User commands, it's pretty worthless: all the OUs are the same and they're tied to the tenant. So I have to find OUs using the Get-ADUser command and then pipe the results - one at a time - to the Get-User (cloud) command. I haven't figured out a way to only find those which are true or false. If I try to specify that, it bombs for those individuals for which it's not true.

This finds the status for all in an OU

Get-ADUser -SearchBase "OU=yourOU,DC=yourDomain,DC=com" -Filter * -SearchScope Subtree -Properties name | % {Get-User $_.Name} | Format-Table -Auto Name,DisplayName,RemotePowerShellEnabled

And this disables all users in an OU

Get-ADUser -SearchBase "OU=yourOU,DC=yourDomain,DC=com" -Filter * -SearchScope Subtree -Properties name | % {Get-User $_.Name | Set-User -RemotePowerShellEnabled $false}

This will bomb if you have any duplicate names

remove shared (delegated) mailboxes for a user - sometimes necessary if the combined size of a user's shared mailboxes exceeds the max that Outlook can handle - see also delegate, remove

First, find out how many delegated mailboxes a user has

$UsersSharedMailboxes = Get-Mailbox | Get-MailboxPermission -user 'someUser@yourDomain.com'

see how big each of these is

$UsersSharedMailboxesStats = $UsersSharedMailboxes | % {Get-MailboxStatistics $_.Identity | Select DisplayName, @{name="TotalItemSize (MB)"; expression={[math]::Round(($_.TotalItemSize.ToString().Split("(")[1].Split(" ")[0].Replace(",","")/1MB),2)}}, ItemCount}

how big is the user's mailbox?

$UsersIndividualMailbox = Get-MailboxStatistics -Identity "someUser@yourDomain.com" | Select DisplayName, @{name="TotalItemSize (MB)"; expression={[math]::Round(($_.TotalItemSize.ToString().Split("(")[1].Split(" ")[0].Replace(",","")/1MB),2)}}, ItemCount

combine both to figure how big users + delegates

$combined = $UsersSharedMailboxesStats

$combined += $UsersIndividualMailbox

now that you've combined them, display them and see if the "TotalItemSize (MB)" adds up to more than 50G

$combined | ogv

choose a few to remove from Outlook

$delegates = @("delegatedUser1@yourDomain.com", "delegatedUser2@yourDomain.com", "delegatedUser3@yourDomain.com")

$delegates | %{Remove-MailboxPermission -Identity $_ -User yourUser@yourDomain.com -AccessRights FullAccess -Confirm:$false}

If you want to add them back again (only this time without them showing up in local Outlook)

$delegates | %{Add-MailboxPermission -Identity $_ -user yourUser@yourDomain.com -AccessRights FullAccess -AutoMapping:$false}

resources, set delegates - see permissions, assign mailbox permissions/delegation of one user to another user

room, create

Create on local AD as a regular user & sync.
Temporarily assign a license, log in as the new user using WebMail to "force" creation of a mailbox.
Make sure the mailbox exists:

Get-Mailbox -Identity MeetingRoom4@yourDomain.com

Convert to a room:

Set-Mailbox -identity MeetingRoom4@yourDomain.com -Type Room

Don't forget to take away the license when you're done.

rooms, set delegates - see permissions, assign mailbox permissions/delegation of one user to another user

rules, list for a single user

Get-InboxRule -mailbox someUser | select name, enabled, description | fl

In order to find out when rules were created, need to search the audit log. This example finds all new email rule creations on a certain day

$auditEventsForUser = Search-UnifiedAuditLog -StartDate '2018-12-14' -EndDate '2018-12-15' -UserIds [someUser] -RecordType ExchangeAdmin -Operations New-InboxRule
$ConvertedOutput = $auditEventsForUser | Select-Object -ExpandProperty AuditData | ConvertFrom-Json

$ConvertedOutput | Select-Object CreationTime,Operation,Workload,ClientIP,Parameters | ft -Wrap -a

rules, list for all users

Get-Mailbox -ResultSize Unlimited | % {Get-InboxRule -Mailbox $_.Alias | Select @{Expression={$_.SendTextMessageNotificationTo};Label="SendTextMessageNotificationTo"},@{Label = "MailboxOwner";Expression = {($_.MailboxOwnerId -Split "/")[3]}}, Name, Priority, Enabled, From , SentTo, CopyToFolder, DeleteMessage, ForwardTo, MarkAsRead, MoveToFolder, RedirectTo} | Export-Csv -Path "$([environment]::GetFolderPath("mydocuments"))\ForwardingRules$((Get-Date).ToString('MM-dd-yyyy_hh-mm-ss')).csv" -NoTypeInformation

This does have problems with putative duplicates

–S–

SendAs permission delegation

check for one person

Get-Mailbox -identity mailboxToAllowPerm | Get-RecipientPermission -AccessRights SendAs -Trustee whoNeedsPerm

delete SendAs permission for every mailbox to which a user has such permission

$DepartingUserIdentity = "bob"

$departedRecipientPerm = Get-Mailbox | Get-RecipientPermission -AccessRights SendAs -Trustee $DepartingUserIdentity
$departedRecipientPerm | % {Remove-RecipientPermission $_.Identity -AccessRights SendAs -Trustee $_.User -Confirm:$False}

grant SendAs permission on a shared mailbox to a delegate's mailbox

Add-RecipientPermission someSharedMailbox@yourDomain.com -AccessRights SendAs -Trustee someUser@yourDomain.com -Confirm:$False

SendOnBehalfTo, add this permission for a user on a shared mailbox - I have seen where attempting to add through the GUI appears as if it gives the proper permissions but really doesn't

first view

Get-Mailbox -identity someSharedMailbox@yourDomain.com | Select-Object GrantSendOnBehalfTo

then change

Set-Mailbox someSharedMailbox@yourDomain.com -GrantSendOnBehalfTo @{add="whoYouWantToHaveAccess@yourDomain.com"}

or

Set-Mailbox 'someSharedMailbox' -GrantSendOnBehalfTo @{add="whoYouWantToHaveAccess@yourDomain.com"}

or even

Set-Mailbox 'someSharedMailbox' -GrantSendOnBehalfTo @{add="whoYouWantToHaveAccess"}

SendOnBehalfTo, find all mailboxes to which a user has been delegated - see delegated mailboxes that a user has SendOnBehalfTo

shared mailbox, convert individual mailbox to shared mailbox

Set-Mailbox "someUser@yourDomain.com" -Type shared

shared mailbox doesn't show up in Outlook - see delegates don't show up as expected in Outlook

shared mailbox, filter out from list of mailboxes- see mailbox types, filter out types

shared mailbox, guest IDs - see guest ID, delegate of shared mailbox

shared mailboxes, list all

Get-Mailbox -RecipientTypeDetails SharedMailbox -ResultSize:Unlimited | Select Identity,Alias,PrimarySmtpAddress,WindowsEmailAddress,UserPrincipalName,DisplayName | sort displayname

shared mailboxes, list all shared mailboxes upon which a user has permissions

full access

Get-Mailbox | Get-MailboxPermission -user "someUser@yourDomain.com"

SendOnBehalfOf

Get-Mailbox | ? {$_.GrantSendOnBehalfTo -match "someUser@yourDomain.com"}

shared mailbox, list delegates

Get-Mailbox -Identity someuser | Get-MailboxPermission | where {($_.IsInherited -eq $False) -and -not ($_.User -like "NT AUTHORITY\SELF")} | ft identity,user,accessrights

shared mailboxes, list who's delegated to each for a domain

First, stash the mailboxes into a variable. If you only want to list the shared mailboxes and don't care about delegates, you can dispense using the variable as an intermediate step and can stop here. But in the next step we'll list the delegates using the contents of this variable.

$mailboxes = Get-Mailbox -RecipientTypeDetails SharedMailbox -ResultSize:Unlimited | `
    where {$_.PrimarySmtpAddress -match "yourdomain.com"} | `
    Select Identity,Alias,DisplayName,user,AccessRights | sort displayname

Now list the delegates for each of these shared mailboxes.

$mailboxes | sort displayname | foreach {Get-MailboxPermission -Identity $_.alias | `
    where {($_.IsInherited -eq $False) -and -not ($_.User -like "NT AUTHORITY\SELF") } | `
    ft identity,user,accessrights} > somefile.txt

shared mailbox, remove automapping for several users - see automap a shared mailbox, remove for several users

size of mailbox - see Get-MailboxStatistics

spam message trace

Show spam for the past week

Get-MessageTrace -Start (Get-Date).AddDays(-7) -End (Get-Date) -Status "FilteredAsSpam" | `
    Export-Csv -Path "$([environment]::GetFolderPath("mydocuments"))\FilteredAsSpam$((Get-Date).ToString('MM-dd-yyyy_hh-mm-ss')).csv"

Note that we put in the FilteredAsSpam as part of the base search rather than piping unfiltered results to a where clause. You can only get up to 1000 records at a time. Depending on how much spam is being filtered, if you don't add the filter right up front, you may get very few records for the week - only the most recent ones. Even with the filter up front, you may not get a whole week's worth

For a particular user's history

$spamTraces = Get-MessageTrace -Start "10/25/2018 5 PM" -End "11/01/2018 4:00 PM" -Sender "someUser@yourDomain.com" | Where {$_.Status -eq "FilteredAsSpam"}

and then expand for more detail

$spamTraces | Get-MessageTraceDetail | Select-Object MessageID, Date, Event, Action, Detail, Data | ogv

special characters, export users into CSV

Get-MsolUser -all | select displayName, userprincipalname | export-csv 'Users.csv' -Encoding UTF8

subject of email, find

Let's say you want to see all the emails with a subject of "Some Important Subject" in Bob Smith's email box. You must specify a "TargetMailbox" and a "TargetFolder". So we'll specify Sam Snead's ("snead") mailbox and "BobSmith" as a directory in Sam Snead's in box:

Search-Mailbox "Bob Smith" -SearchQuery 'Subject:"Some Important Subject"' -TargetMailbox snead -TargetFolder BobSmith

As soon as the command above finishes, Sam Snead will see a new "BobSmith" as a directory in Sam Snead's in box (in Outlook). Under that he'll see a directory named something like "Bob Smith-11/5/2018 8:54:34 PM" and under that "Primary Mailbox" and then under that a list of the various folders which might contain the emails.

Actually, you don't really have to specify a "TargetMailbox" and a "TargetFolder" if all you want to do is delete these. If that's all you want, then see delete emails where you'll see that you can simply specify -DeleteContent there instead.

See here for discussion and more examples.

switch users

sometimes we want to move a user to a whole new division but leave their mailbox behind for their supervisor or successor to inherit. We want the user's local AD to stay intact so all their bookmarks, cookies, passwords, etc. stay intact. So we'll create a whole new ID in their old division, move the user's old mailbox over to the new ID, convert the new ID to a shared mailbox, and delegate that to their successor

So create the new user. Use different userPrincipalName, targetAddress. Leave proxyAddresses blank for now.

Stash the old ImmutableIDs

$oldUserImmutableID = (Get-MsolUser -UserPrincipalName oldUser@yourDomain.com).ImmutableID

$newUserImmutableID = (Get-MsolUser -UserPrincipalName newUser@yourDomain.com).ImmutableID

Change UPN, email, main proxyAddress for old user.

The next step is very important: make sure that your old user doesn't have its targetAddress or proxyAddresses duplicated somewhere else.

$userString = "oldUserNameFragment"
Get-MsolUser | where {($_.userprincipalname -match "$userString") -or ($_.ProxyAddresses -match "$userString") } | `
    Select-Object DisplayName, UserprincipalName, ProxyAddresses
Get-ADUser -Filter "ProxyAddresses -like '*$userString*'" -Properties DisplayName, UserprincipalName, ProxyAddresses, TargetAddress | `
    Select-Object DisplayName, UserprincipalName, ProxyAddresses, TargetAddress | ogv
Get-ADUser -Filter "TargetAddress -like '*$userString*'" -Properties DisplayName, UserprincipalName, ProxyAddresses, TargetAddress | `
    Select-Object DisplayName, UserprincipalName, ProxyAddresses, TargetAddress | ogv

Carefully examin for dupes. If you're satisfied, sync. If you missed a dupe, the next steps will work but you won't be able to create a new mailbox for the old user.

Assign old email proxy to new user. Sync. Move both new and old users out of OUs that are synced to an OU that isn't. Sync again. This will soft-delete both users so we can muck around with their ImmutableIDs. Verify that both are soft deleted.

Get-MsolUser -All -ReturnDeletedUsers | Sort-Object UserPrincipalName | ft UserPrincipalName, DisplayName, ObjectId, ImmutableID

Restore users as "floaters"

Verify

Clear immutableIds of floaters

Switch ImmutableIDs

Verify reassigned ImmutableIDs

Restore both soft-deleted users by moving their IDs back to synced OUs in local AD and syncing. This is where, if you weren't careful with making sure the targetAddresses and proxyAddresses were unique, problems.

Sync Errors - see DirSync errors, list

–T–

targetAddress for contacts - although local AD contacts have "targetAddress", on Office 365 this property translates to "externalEmailAddress" - see contacts, display proxyAddresses and targetAddress

time of last mailbox login - see Get-MailboxStatistics

time zone bulk change

set all mailboxes in our Office 365 Tenant to W. Europe Standard Time

Get-mailbox -ResultSize unlimited | Set-MailboxRegionalConfiguration -Language 1031 -TimeZone "W. Europe Standard Time" -LocalizeDefaultFolderName

as well as set language to German and change all of the default folders

trace message - (for emails, not calendars; for calendars, see calendar activity, examine log instead) - see also spam message trace

Log times are in UTC - which isn't necessarily the same time zone that your emails are in. If you don't want to mess with UTC offset in the command and just want to find by UTC:

Get-MessageTrace -Start "9/20/18 8 pm" -End "9/20/18 9 pm" -Sender "haplessUser@yourDomain.com" | Select-Object Received, SenderAddress, RecipientAddress, Subject, Status, ToIP, FromIP, Size, MessageID, MessageTraceID | Where {$_.Status -eq "Failed"} | Out-GridView

But it's useful to search for messages using the same time as your local time zone. So start by finding the offset:

$rawUTCOffset = (([TimeZoneInfo]::Local).BaseUtcOffset).TotalHours
$adjustedUTCOffset = if ((Get-Date).IsDayLightSavingTime()) {$rawUTCOffset+1} else {$rawUTCOffset}

Once you know offset, you can more accurately find stuff by searching for time spans that make sense to you in the time zone where you are:

Get-MessageTrace -Start ([datetime]"10/11/2018 11:30 PM").AddHours($adjustedUTCOffset*(-1)) -End ([datetime]"10/11/2018 11:50 PM").AddHours($adjustedUTCOffset*(-1)) `
    -Sender "some.user@yourDomain.com" | Where {$_.Subject -like "Email subject*"} |`
    Select-Object @{n="UTReceived";e={$_.Received}}, @{n="YourLocalTime";e={$_.Received.AddHours($adjustedUTCOffset)}}, `
    @{n="SomeOtherTimeMaybeEurope";e={$_.Received.AddHours(1)}}, SenderAddress, RecipientAddress, Subject, Status, size | ogv

trace message including a wild card

Get-MessageTrace -Sender "someUser@yourDomain.com" -Start (Get-Date).AddDays(-7 ) -End (Get-Date) | ? {$_.RecipientAddress –like "someOtherUser*"} | ogv

you can put in wildcard ("*") immediately before or after the "@" for the recipient or sender. But I haven't had luck putting something like "@yourDomain.*" - system complains "Invalid RecipientAddress value"

filtering on subject:

Get-MessageTrace -Start "9/7/18 1:50 pm" -End "9/7/18 11 pm" | Where {$_.Subject -like "*xyz*"} | Out-GridView

trace message that have errors

You can also get more detail on errors. You can start with the general info (usually with a very narrow time window which I determine by looking at previous Get-MessageTrace statements without specifying Failed):

Get-MessageTrace -Start "10/03/18 10:25:30 am" -End "10/03/18 10:25:35 am" | Where {$_.Status -eq "Failed"} | `
    Select-Object Received, SenderAddress, RecipientAddress, Subject, Status, ToIP, FromIP, Size, MessageID, MessageTraceID |`
    Export-Csv -Path "$([environment]::GetFolderPath("mydocuments"))\failedDeliveries$((Get-Date).ToString('MM-dd-yyyy_hh-mm-ss')).csv"

and then pipe that same statement into another Get-MessageTraceDetailstatement to get the details associated with the failed emails:

Get-MessageTrace -Start "10/03/18 10:25:30 am" -End "10/03/18 10:25:35 am" | Where {$_.Status -eq "Failed"} `
    | Get-MessageTraceDetail | Select-Object MessageID, Date, Event, Action, Detail, Data | `
    Export-Csv -Path "$([environment]::GetFolderPath("mydocuments"))\failedDeliveryDetails$((Get-Date).ToString('MM-dd-yyyy_hh-mm-ss')).csv"

If you run both these, to make sense of how they relate to each other, you'll have to open both files side-by-side. But there's a better way where we can see elements of the message we attempted to send along with the error it encountered. This time we do specify fail:

$rawUTCOffset = (([TimeZoneInfo]::Local).BaseUtcOffset).TotalHours
$adjustedUTCOffset = if ((Get-Date).IsDayLightSavingTime()) {$rawUTCOffset+1} else {$rawUTCOffset}
$failedTraces = Get-MessageTrace -Start ([datetime]"10/11/2018 11:30 PM").AddHours($adjustedUTCOffset*(-1)) -End ([datetime]"10/11/2018 11:50 PM").AddHours($adjustedUTCOffset*(-1)) `
    -Sender "haplessUser@yourDomain.com" | Where {$_.Subject -like "some subject*"} | Where {$_.Status -eq "Failed"}
$failedTraces | Foreach-Object{
    $trace = $
    $stats = $trace | Get-MessageTraceDetail -event FAIL
    New-Object -TypeName PSObject -Property @{
        MessageUTCTime = $trace.Received
        MessageLocalTime = $trace.Received.AddHours($adjustedUTCOffset)
        MessageEuropeTime = $trace.Received.AddHours(1)
        Sender = $trace.SenderAddress
        Recipients = $trace.RecipientAddress
        Subject = $trace.Subject
        MessageSize = $trace.Size
        StatusMessage = $stats.Detail
    }} | ogv

For some reason, the order of the columns is completely different from the order above.

For emails older than a week, need to run Start-HistoricalSearch instead.

traffic (email) - see email traffic

–U–

unified group, bulk change email addresses

"unified groups" include

Let's say we want to find all the groups belonging to the "yourdomain" domain and purge all emailAddresses for that same domain. Find all the groups that fit this profile and put it in a variable:

$UnifiedGroup = Get-UnifiedGroup | where-Object {$_.emailAddresses -like "*yourdomain.com" }

Optional: inspect first before proceding to the command that actually applying our changes:

$UnifiedGroup | ft name, emailAddresses

Now proceed to actually do what we set out to do: remove all "emailAddresses" corresponding to our domain:

$UnifiedGroup | % {Set-UnifiedGroup -identity $_.identity -emailAddresses @{remove = "smtp:" + $_.PrimarySmtpAddress.split("@")[0] +"@yourdomain.com"}}

Note that, unlike many other objects, unified groups use "emailAddresses" much the same way as other objects (such as users) use "proxyAddresses". Also note that we could have done all this in one command without the intermediate variable. But it's nice to actually see the group we intend to change things before we actually apply changes (using the Set-UnfiedGroup command) just to make sure.

unified group, bulk change primary SmtpAddress

$UnifiedGp = Get-UnifiedGroup | ? {$_.isdirsynced -eq 0 -and ($_.PrimarySmtpAddress.split("@")[1] -match "yourdomain.com")}

Optional: inspect first before proceding to the command that actually applying our changes:

$UnifiedGp | ft name, emailAddresses

Now proceed to actually do what we set out to do: set "PrimarySmtpAddress" for all users which had corresponding "PrimarySmtpAddress" correpsonding to our domain:

$UnifiedGp | % {Set-UnifiedGroup -identity $_.identity -primarysmtpaddress ($_.PrimarySmtpAddress.split("@")[0] +"@yourTenant.onmicrosoft.com")}

Note that we could have done all this in one command without the intermediate variable. But it's nice to actually see the group we intend to change things before we actually apply changes (using the Set-UnfiedGroup command) just to make sure.

–W–

We are preparing a mailbox for this user - see preparing a mailbox for this user

–X–

–Y–

–Z–