Reconnect an Exchange 2013 Mailbox

Scenario:  A mailbox is disabled from an AD user account but it’s still present in Exchange as a disconnected Mailbox.  To view the disconnected mailbox:   Get-MailboxStatistics | Where DisconnectDate -ne $null

Resolution:  Run the following commands:

Connect-Mailbox “Display Name” -Database “DB Name” -user “AD User Object Name”

Example:

Connect-Mailbox “John Jacob” -Database DB03 -user “jjacob”

Exchange Script to Export Hardware Inventory and Exchange Settings

Scenario:    You want to export hardware inventory and Exchange settings from your Exchange Organization.

Script:  You can copy all the script parts and run at once OR run each script part separately depending on what you want to achieve.  You could also build on to this script using the same format with different get commands.

 

#Specify Variables
$today = (get-date -f yyyy-MM-dd)
$Folder = "C:TempCollection"
#Pull Exchange Server Hardware Inventory
$Servers = Get-ExchangeServer
$Servers | Export-csv $folder$today-Get-ExchangeServer.csv
($servers).name | Out-File $folder$today-Servers.txt
$serverList = "$folder$today-Servers.txt"
$outputCSV = "$folder$today-ServerInventory.csv"
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
pushd $dir
[System.Collections.ArrayList]$sysCollection = New-Object System.Collections.ArrayList($null)
foreach ($server in (Get-Content $serverList))
{
    "Collecting information from $server"
    $totCores=0
  
    try
    {
        [wmi]$sysInfo = get-wmiobject Win32_ComputerSystem -Namespace "rootCIMV2" -ComputerName $server -ErrorAction Stop
        [wmi]$bios = Get-WmiObject Win32_BIOS -Namespace "rootCIMV2" -computername $server
        [wmi]$os = Get-WmiObject Win32_OperatingSystem -Namespace "rootCIMV2" -Computername $server
        #[array]$disks = Get-WmiObject Win32_LogicalDisk -Namespace "rootCIMV2" -Filter DriveType=3 -Computername $server
        [array]$disks = Get-WmiObject Win32_LogicalDisk -Namespace "rootCIMV2" -Computername $server
        [array]$procs = Get-WmiObject Win32_Processor -Namespace "rootCIMV2" -Computername $server
        [array]$mem = Get-WmiObject Win32_PhysicalMemory -Namespace "rootCIMV2" -ComputerName $server
        [array]$nic = Get-WmiObject Win32_NetworkAdapterConfiguration -Namespace "rootCIMV2" -ComputerName $server | where{$_.IPEnabled -eq "True"}
  
        $si = @{
            Server          = [string]$server
            Manufacturer    = [string]$sysInfo.Manufacturer
            Model           = [string]$sysInfo.Model
            TotMem          = "$([string]([System.Math]::Round($sysInfo.TotalPhysicalMemory/1gb,2))) GB"
            BiosDesc        = [string]$bios.Description
            BiosVer         = [string]$bios.SMBIOSBIOSVersion+"."+$bios.SMBIOSMajorVersion+"."+$bios.SMBIOSMinorVersion
            BiosSerial      = [string]$bios.SerialNumber
            OSName          = [string]$os.Name.Substring(0,$os.Name.IndexOf("|") -1)
            Arch            = [string]$os.OSArchitecture
            Processors      = [string]@($procs).count
            Cores           = [string]$procs[0].NumberOfCores
            NIC      = [string]$nic[0].IPAddress
        }
         
        $disks | foreach-object {$si."Drive$($_.Name -replace ':', '')"="$([string]([System.Math]::Round($_.Size/1gb,2))) GB"}
    }
    catch [Exception]
    {
        "Error communicating with $server, skipping to next"
        $si = @{
            Server          = [string]$server
            ErrorMessage    = [string]$_.Exception.Message
            ErrorItem       = [string]$_.Exception.ItemName
        }
        Continue
    }
    finally
    {
       [void]$sysCollection.Add((New-Object PSObject -Property $si))   
    }
}
 
$sysCollection `
    | select-object Server,NIC,TotMem,OSName,Arch,Processors,Cores,Manufacturer,Model,BiosDesc,BiosVer,BiosSerial,DriveA,DriveB,DriveC,DriveD,DriveE,DriveF,DriveG,DriveH,DriveI,DriveJ,DriveK,DriveL,DriveM,DriveN,DriveO,DriveP,DriveQ,DriveR,DriveS,DriveT,DriveU,DriveV,DriveW,DriveX,DriveY,DriveZ,ErrorMessage,ErrorItem `
    | sort -Property Server `
    | Export-CSV -path $outputCSV -NoTypeInformation  
#Export ClientAccess Settings
$CASServers = Get-ClientAccessServer 
$CASServers | %{Get-OWAVirtualDirectory -server $_.name  | Export-csv $folder$today-Get-OWAVirtualDirectory.csv -append}
$CASServers | %{Get-ECPVirtualDirectory -server $_.name  | Export-csv $folder$today-Get-ECPVirtualDirectory.csv -append}
$CASServers = Get-ClientAccessServer 
$CASServers | %{Get-OWAVirtualDirectory -server $_.name  | Export-csv $folder$today-Get-OWAVirtualDirectory.csv -append}
$CASServers | %{Get-ECPVirtualDirectory -server $_.name  | Export-csv $folder$today-Ge$CASServers | %{Get-OABVirtualDirectory -server $_.name  | Export-csv $folder$today-Get-OABVirtualDirectory.csv -append}
$CASServers | %{Get-MAPIVirtualDirectory -server $_.name  | Export-csv $folder$today-Get-MAPIVirtualDirectory.csv -append}
$CASServers | %{Get-WebServicesVirtualDirectory -server $_.name  | Export-csv $folder$today-Get-ServicesVirtualDirectory.csv -append}
$CASServers | %{Get-ActivesyncVirtualDirectory -server $_.name  | Export-csv $folder$today-Get-EASVirtualDirectory.csv -append}
$CASServers | %{Get-OutlookAnywhere -server $_.name | Export-csv $folder$today-Get-OutlookAnywhere.csv -append}
#Export Database Settings
$DB = Get-mailboxdatabase 
$DB | Export-csv $folder$today-Get-MailboxDatabase.csv -append
#Export DAG Settings
$DAG  = Get-DatabaseAvailabilityGroup 
$DAG | Export-csv $folder$today-Get-DatabaseAvailabilityGroup.csv

Determine what roles are needed to run specific commands when determining Role Groups

Scenario:  You want to provide only the roles necessary to a new role group based on the Exchange Shell commands that need to be ran.

Solution:  Run the following to determine which roles will need to be added to the role group:

Get-ManagementRoleEntry *Set-ActiveSyncMailboxPolicy  | Select Name,Role

Name                           Role
Set-ActiveSyncMailboxPolicy    Recipient Policies

Can not connect to a mailbox after reconnecting from disconnected state.

Scenario: After you reconnect a disconnected mailbox, you may receive the following error in Outlook Web App, ​ ‘Your mailbox has been disabled.’ You may also have trouble connecting to the mailbox in powershell or EMC saying the mailbox is not available.

Solution: Run the following command in Exchange Powershell: Update-StoreMailboxState -Database “db_name” -Identity “mailbox_guid”

Note: If you want to update the mailbox state for all mailboxes on a particular database

Get-MailboxStatistics -Database “db_name” | ForEach {Update-StoreMailboxState -Database $_.Database -Identity $_.MailboxGuid -Confirm:$False}

 

A mailbox sends out to more recipients than allowed by the recipient rate limit in their throttling policy

Scenario:  A mailbox has sent to more recipients than allowed by the recipient rate limit that is set in their throttling policy.  For example, a mailbox can only send out to  100 recipients within a 24 hour period, BUT that mailbox has managed to send out to 500 recipients. How can this be?

Whats happening:  A mailbox can send to more than 100 recipients if:

  1. The 24 hours have been completed for that mailbox. The will be able to send for up to another 100 recipients.
  2. The throttling service  has been restarted on the mailbox server where the mailbox is located.
  3. If a user sends to a distribution list, as the distribution list will only be counted as one recipient EVEN though there are 400 recipients in that distribution list.   

Here is the WorkFlow of the RecipientRateLimit in the throttling Policy.

Terminology:

Token: The token is the number of recipients which has already been processed

Token Bucket Map:  This is the table where the guids of the mailboxes are kept with the current RecipientRateLimit.

The token bucket map is held in memory when the Throttling service is started on the Mailbox Server. As new mailboxes send mail, they are added to the bucket map.  When the mailbox submits mail, the throttling service checks the bucket map to obtain the token for the sender.  In order for messages to be sent by the user, this must be true:

Obtain Token + Token Bucket Map <= RecipientRateLImit

Example: If the mailbox is trying to send 10 recipients ( Obtain Token) but the user has previously sent to 96 recipients (Token Bucket Map),  sending would fail:  96 + 10 <= 100

But if the mailbox is sending to 2 recipients (and not 10), sending would succeed:   96 + 2 <= 100

Distribution groups are counted as one sender  and further into the transport process of the sent message the  distribution groups is expanded into numerous recipients.  Sending a message from a mailbox always originates from the mailbox server where the mailbox is located on.  This is the mailbox server holds the Token Bucket Map.  The expansion of the distribution group can happen on any server during the transport process, thus its not logically possible to store the true number of recipients for that sender when a distribution group is used. This is why the Distribution Group is a loophole to the Recipient Rate Limit.

Script to Recreate Exchange Databases

Scenario:  You want to recreate an Exchange Database in order to reclaim the Windows space that the database was utilizing.  You have already moved all mailboxes off of the database.

 Script: This script performs the following:

  • Checks to make sure no mailboxes reside on the database
  • Removes All Database Copies
  • Removes the Databases from the folder directories ( You may have to edit this)
  • Creates the Database and sets the quotas to unlimited
  • Creates the Database Copies
  • Resolves any errors if the database copies are not healthy.
#Set DB Variable
$DB = "DB01"

#Sets ADServerSettings to see all Arbiratrion mailboxes
Set-ADServerSettings -ViewEntireForest $true

#Check for any mailboxes on the database
$1 = (get-mailbox -database $DB).count
$1 += (get-mailbox -database $DB -arbitration).count
$1 += (Get-mailbox -database $DB -archive).count
$1 += (Get-mailbox -database $DB -publicfolder).count
If ($1 -gt 0){
"Mailboxes still exist on this database. Please remove all mailboxes and run this script again."
}
Else
{
"Starting the Database Recreation Process"
"Determining Activation Preference"

#Gathers the Copies
$AP = Get-MailboxDatabaseCopyStatus $DB  | Sort ActivationPreference | Select -expandproperty Mailboxserver
$firstServer = $AP | Select -first 1
$OthServers = Get-MailboxDatabaseCopyStatus $DB | Where ActivationPreference -ne 1 | Sort ActivationPreference | Select -ExpandProperty MailboxServer
Write-Host "The Database is hosted on these 4 copies:  $AP"

#Remove all Mailbox Database copies from each server
Write-Host "Removing Database Copies"
get-mailboxdatabasecopystatus $DB | Where status -notlike *Mount* | Remove-mailboxdatabasecopy -confirm:$false 
remove-mailboxdatabase $DB -confirm:$false 

Write-Host "Sleeping 10 minutes for all processes to become unlocked"
Sleep 600
Write-Host "Deleting Folder structure on all copies"

#Delete Folder Structure
$AP | %{Remove-Item \$_c$$DBDB -force -recurse }
$AP | %{Remove-Item \$_c$$DBLogs -force -recurse }

#Create the DB
Write-Host "Creating $db"
New-MailboxDatabase $db -EDBFilePath C:$dbDB$db.edb -LogFolderPath C:$dbLogs -Server $firstserver 

#Wait for replication
Write-Host "Sleeping 3 minutes for replication"
Sleep 180

#Mount the Database
Write-Host "Mounting $db"
Mount-Database $db

#Wait for additional replication
Write-Host "Sleeping 3 minutes for replication"
Sleep 180

#Setting the DB Quotas
Write-Host "Setting DB Quotas to unlimited"
Set-MailboxDatabase $db -ProhibitSendReceiveQuota Unlimited -ProhibitSendQuota Unlimited -IssueWarningQuota Unlimited

#Create the additional Database Copies
$OthServers | %{
Write-Host "Creating DB copy for $DB on $_"
Add-MailboxDatabaseCopy $db -MailboxServer $_ 
}
#Resumes the DB Copies if they are not currently healthy
get-mailboxdatabasecopystatus $DB | Where status -notlike "Mounted" | Resume-mailboxdatabasecopy
}

 

Checking Digital Signatures of a specific file against multiple servers

Scenario:  The other day an article was written by ARS Technica on a mail server attack that steals massive number of passwords. One of the symptoms is finding an unsigned OWAAuth.DLL and in some cases the file was located in a different directory.

Solution: Here is a quick way to check all of your Exchange servers to make sure all of your OWAAUTH.DLL files are signed and in the correct path:

#Build your Servers Variable
$Servers = Get-exchangeserver
#Build your Auth Variable
$Auth = $Servers |  %{Get-childitem -path "\$_c$program filesMicrosoftExchange Serverv15" -filter owaauth.dll -recurse | Get-authenticodeSignature}
#Export your Auth Variable to read it in Excel
$Auth | Export-csv C:tempAuth.csv

 

Some internal recipients are not receiving messages when sent to Distribution Groups

Scenario:  Users are complaining that they sporadically miss email communications when the emails were sent to a specific distribution groups.  Upon further investigation of the distribution groups, the recipients that did not receive the email were members of other distribution groups that were nested two,three, and/or four times of that distribution group that was originally sent to.

Troubleshooting:  After pulling the message tracking log against all exchange servers, we noticed that the distribution groups were not expanding by seeing a 0 recipient count:

get-transportserver | Get-messagetrackinglog -subject “Testing” -resultsize unlimited -eventID Expand | Select RecipientCount,RelatedRecipientAddress

After investigating the distribution groups that were not expanding, we found that these distribution groups were mail enabled security groups AND they were set to Global and not Universal.  Apparently these groups were changed back to Global after the mail enabled security groups were created so they could be members of other global distribution groups. Thus we were experiencing ‘Black Hole’ distribution groups.

Solution:  After the user changed all the groups from Global to Universal, mail flow returned to normal.

EWS Script to Export Calendar Items to a CSV file via PowerShell

Scenario:  You need a script to export the calendar items of a mailbox into a CSV file.

Solution: Use the script below to export the calendar items of a mailbox into a CSV file.  Please note that the date range has a 2 year/1000 appointment max.  Depending on the number of items in the calendar between your date range, you may have to adjust the date range to accommodate the 1000 item limit. Also, give yourself full access to the mailbox you are querying. I used a section of Glen’s script because he nailed the attendee’s query. Props to Glen!

#Declare Variables
$EWSDLL = "C:Program FilesMicrosoftExchange ServerV15BinMicrosoft.Exchange.WebServices.dll"
$MBX = "teststeve@domain.com"
$EWSURL = "https://Ex2013Svr1.domain.com/EWS/Exchange.asmx"
$StartDate = (Get-Date).AddDays(-60)
$EndDate = (Get-Date)  

#Binding of the calendar of the Mailbox and EWS
Import-Module -Name $EWSDLL
$mailboxname = $MBX
$service = new-object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.Exchangeversion]::exchange2013)
$service.Url = new-object System.Uri($EWSURL)
$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar,$MailboxName) 
$Calendar = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
$Recurring = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Appointment, 0x8223,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Boolean); 
$psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)  
$psPropset.Add($Recurring)
$psPropset.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text;

$RptCollection = @()

$AppointmentState = @{0 = "None" ; 1 = "Meeting" ; 2 = "Received" ;4 = "Canceled" ; }

#Define the calendar view  
$CalendarView = New-Object Microsoft.Exchange.WebServices.Data.CalendarView($StartDate,$EndDate,1000)    
$fiItems = $service.FindAppointments($Calendar.Id,$CalendarView)
if($fiItems.Items.Count -gt 0){
 $type = ("System.Collections.Generic.List"+'`'+"1") -as "Type"
 $type = $type.MakeGenericType("Microsoft.Exchange.WebServices.Data.Item" -as "Type")
 $ItemColl = [Activator]::CreateInstance($type)
 foreach($Item in $fiItems.Items){
  $ItemColl.Add($Item)
 } 
 [Void]$service.LoadPropertiesForItems($ItemColl,$psPropset)  
}
foreach($Item in $fiItems.Items){      
 $rptObj = "" | Select StartTime,EndTime,Duration,Type,Subject,Location,Organizer,Attendees,AppointmentState,Notes,HasAttachments,IsReminderSet
 $rptObj.StartTime = $Item.Start  
 $rptObj.EndTime = $Item.End  
 $rptObj.Duration = $Item.Duration
 $rptObj.Subject  = $Item.Subject   
 $rptObj.Type = $Item.AppointmentType
 $rptObj.Location = $Item.Location
 $rptObj.Organizer = $Item.Organizer.Address
 $rptObj.HasAttachments = $Item.HasAttachments
 $rptObj.IsReminderSet = $Item.IsReminderSet
 $aptStat = "";
 $AppointmentState.Keys | where { $_ -band $Item.AppointmentState } | foreach { $aptStat += $AppointmentState.Get_Item($_) + " "}
 $rptObj.AppointmentState = $aptStat 
 $RptCollection += $rptObj
 foreach($attendee in $Item.RequiredAttendees){
  $atn = $attendee.Address + "; "  
  $rptObj.Attendees += $atn
  }
 foreach($attendee in $Item.OptionalAttendees){
  $atn = $attendee.Address + "; "  
  $rptObj.Attendees += $atn
 }
 foreach($attendee in $Item.Resources){
  $atn = $attendee.Address + "; "  
  $rptObj.Resources += $atn
 }
 $rptObj.Notes = $Item.Body.Text
#Display on the screen
 "Start:   " + $Item.Start  
 "Subject: " + $Item.Subject 
}   
#Export to a CSVFile
$RptCollection |  Export-Csv -NoTypeInformation -Path "c:temp$MailboxName-CalendarCSV.csv"

Replace A Mailbox’s Explicit Full Access Permission With A Mail Enabled Security Group To Allow Non-Admins To Manage Access.

Scenario:  Currently you have mailboxes that have explicit full access to a shared mailbox. Instead of giving full access permission at the root mailbox level, you want an automated way to define the full access permission to a mail enabled security group. You would like the distribution group populated with those mailboxes who had the explicit full access permission at the root mailbox level and then to remove those users explicit permission at the root mailbox level.  What you will be left with is a populated mail enabled security group with full permissions to a shared mailbox.

One of the biggest benefits for this is that you can assign a user as a  manager/owner of the distribution group who does not need Exchange permission.  They can then modify the membership of the group, thus giving or taking away full permission to other users for the shared mailbox without a call to the help desk.

Solution:  The script below will create a mail enabled security group which will be prefixed with ‘grp-‘, add the membership of the group for those users explicitly defined with full access to the shared mailbox and send-as permissions, add the group as having full control to the shared mailbox, and then remove the users explicit full access permission from the shared mailbox.

All you need is to either query, or import from a csv file, a list of shared mailboxes. If you use a csv file, make sure the column header is labeled Name.

#This script replaces a users explicit full access permission to a mailbox with a Mail Enabled Security Group that will have full access permission.

#This script will automatically gather those explicit users with full access permissions and put them into the mail enabled security group.

#This script give also make a specific user managedby permissions for the group.

#Import service accounts from CSV
$SvcMbx = Import-csv C:tempsvcmbx.csv

#Loop through each Service Mailbox
$SvcMbx | %{

#Define Variables
$N = $_.Name
$g = "GRP-"+$N
Write-Host "Now starting this service mailbox:$N"

#Gather FullMailbox permission with Explicit Access
$explicitmembers = (Get-MailboxPermission $N | Where {($_.IsInherited -eq $false) -and ($_.User -notlike '*Authority*') -and ($_.AccessRights -like "FullAccess")}).user

#Create the Mail Enabled Security Group and add the manager(s) of the group. Then lock the group down so it cannot accept messages except from the manager.
Write-Host "Creating the AD Universal Security Group"
New-ADGroup -name $g -GroupScope Universal -Path "OU=TestGroups,DC=domain,DC=com" -DisplayName $g 
Write-Host "Waiting for Replication"
Sleep 30
Write-Host "Provision the AD group as a Mail enabled Security Group"
Enable-DistributionGroup $g 
Write-Host "Waiting for Replication"
Sleep 30
Write-Host "Configuring the Mail Enabled Security Group:$g"
Set-distributionGroup $g -managedby "jdoe1" -bypasssecuritygroupmanagercheck -acceptmessagesonlyfrom "jdoe1" 

#Populate the explicit mailboxes that have permission to the service mailbox to the new mail enabled distribution group

Write-Host "Populating the Mail Enabled Security Group: Group:$g"
$explicitmembers | %{
Add-DistributionGroupMember $g -member $_.RawIdentity -bypasssecuritygroupmanagercheck 
}

#Add the mail enabled security group to the service mailbox with full permission and automapping set to false
Write-Host "Adding $g with Full Permission and Send As permission to $n" 
Add-mailboxpermission $n -user $g -accessrights fullaccess -automapping $false 
Add-ADPermission $n -user $g -extendedRights 'Send-As'

#Remove the users explicit access to the service mailbox
Write-host "Removing the users Explicit access to $n"
$explicitmembers | %{
Remove-mailboxPermission $n -user $_.RawIdentity -accessrights FullAccess -confirm:$false 
}
}