Exchange PowerShell: Export a mailbox into multiple psts by size using native Exchange commands

Scenario:  Using native Exchange PowerShell commands, export a single mailbox into multiple 10GB PSTs (or another size). This is to mimic the eDiscovery tool in which it makes multiple PST’s that stay around 10GB each.

The script/commands below will perform a search mailbox until it hits the 10000 item limit.  When it hits the 10,000 item limit, it finds the previous  date where the search results is under the 10,000 item limit and records it.  Then the script will take all the dates it records and add the size until it hits around 10GB.  It records those dates in a array and then exports to a PST.

Script:

#Initialize Variables###########################################
$mbx = “Steve”
$stat = Get-mailboxstatistics $mbx | Select DisplayName,TotalDeletedItemSize, TotalItemSize,ItemCount
$TotalSize = [int]$stat.TotalItemSize.Value.ToMB() + [int]$stat.TotalDeletedItemSize.Value.ToMB()
$SizeforPST = [int]10240 ; “$MBX : Total Size $totalSize MB”
$CutThePST = “Yes”
$PSTFilePath =”\ExSvr1PSTFILES”
###############################################################

#If MBX totalsize is greater than $SizeforPST ###############################################################
If($TotalSize -gt $SizeforPST){

“Entering If Statement:  TotalSize ($TotalSize) is greater than $SizeForPST”

#Find Oldest Item in Mailbox to know when to stop searching
$OldestItem = Get-MailboxFolderStatistics $mbx -IncludeOldestAndNewestItems | Where OldestItemReceivedDate -ne $Null | Sort OldestItemReceivedDate | Select -ExpandProperty OldestItemReceivedDate -first 1
$oldestItem = $oldestItem.AddMonths(-1)  #Subtract a month to ensure that search goes before oldest item.
$OldestItem = $OldestItem.ToSTring((“M/d/yyyy”))
#Set Start and End Month 1 Month Apart
$end = get-date -Format “M/d/yyyy”
$start = [datetime]$end
$start= $start.AddMonths(-1)
$start = $start.ToString((“M/d/yyyy”))

#Initialize $results Variable and Final variable
$Results = Search-Mailbox $mbx -SearchDumpster -SearchQuery “Received:$start..$End” –estimateresultonly | Select ResultItemsCount,@{n=’Size’;e={([Microsoft.Exchange.Data.ByteQuantifiedSize]$_.ResultItemsSize).ToMb()}}
$final = @()

#Loop It –
Do{
If($results.ResultItemsCount -ge 10000){
Do{
$start= [datetime]$start
$start= $start.AddDays(5)
$start = $start.ToString((“M/d/yyyy”))
$Results = Search-Mailbox $mbx -SearchDumpster -SearchQuery “Received:$start..$End” –estimateresultonly | Select ResultItemsCount,@{n=’Size’;e={([Microsoft.Exchange.Data.ByteQuantifiedSize]$_.ResultItemsSize).ToMb()}}
“ResultCount: $results.ResultItemsCount between $start – $end”
}While($Results.ResultItemsCount -ge 10000)
}

If($Results.ResultItemsCount -lt 10000){
Do{
If([datetime]$Start -lt [datetime]$oldestitem){Break}
$start= [datetime]$start
$start= $start.AddMonths(-1)
$start = $start.ToString((“M/d/yyyy”))
$Results = Search-Mailbox $mbx -SearchDumpster -SearchQuery “Received:$start..$End” –estimateresultonly | Select ResultItemsCount,@{n=’Size’;e={([Microsoft.Exchange.Data.ByteQuantifiedSize]$_.ResultItemsSize).ToMb()}}
“ResultCount: $results.ResultItemsCount between $start – $end”
}While($Results.ResultItemsCount -lt 10000)

#Since ResultItemCount just became greater than or equal too 10000, subtract add a month to start.
$start= [datetime]$start
$start= $start.AddMonths(1)
$start = $start.ToString((“M/d/yyyy”))
$Results = Search-Mailbox $mbx -SearchDumpster -SearchQuery “Received:$start..$End” –estimateresultonly | Select ResultItemsCount,@{n=’Size’;e={([Microsoft.Exchange.Data.ByteQuantifiedSize]$_.ResultItemsSize).ToMb()}}

}

“Result Count: $($results.ResultItemsCount) Size $($results.size)”

#Build the Array
$ServerObj = New-Object PSObject
$ServerObj | Add-Member NoteProperty -Name “Alias” -Value $mbx
$ServerObj | Add-Member NoteProperty -Name “Start” -Value $start
$ServerObj | Add-Member NoteProperty -Name “End” -Value $end
$ServerObj | Add-Member NoteProperty -Name “ItemCount” -Value $results.ResultItemsCount
$ServerObj | Add-Member NoteProperty -Name “Size” -Value $results.Size
$Final += $ServerObj

#Recalibrate Variables
$end = $start
$start = [datetime]$end
$start= $start.AddMonths(-1)
$start = $start.ToString((“M/d/yyyy”))
$Results = Search-Mailbox $mbx -SearchDumpster -SearchQuery “Received:$start..$End” –estimateresultonly | Select ResultItemsCount,@{n=’Size’;e={([Microsoft.Exchange.Data.ByteQuantifiedSize]$_.ResultItemsSize).ToMb()}}

“While Start: $start > OldestItem: $oldestitem”
}While([datetime]$start -ge [datetime]$OldestItem)

$ServerObj = New-Object PSObject
$ServerObj | Add-Member NoteProperty -Name “Alias” -Value $mbx
$ServerObj | Add-Member NoteProperty -Name “Start” -Value $start
$ServerObj | Add-Member NoteProperty -Name “End” -Value $end
$ServerObj | Add-Member NoteProperty -Name “ItemCount” -Value $results.ResultItemsCount
$ServerObj | Add-Member NoteProperty -Name “Size” -Value $results.Size
$Final += $ServerObj
###########################################################################################################

#Create a PST Hash Table

$PST_Final = @()
$PSTSize = 999999

$Final | %{
“____________________________________”
“Starting Loop”

if($PSTSize -eq 999999){

$PSTSize = $_.Size
$PST_end = $_.End
$PST_Start = $_.Start
“1st Loop: $PST_Start — $PST_end — $PSTSize”

}else{

“Entering Else Loop”
$PST_Start_temp = $_.start
$PST_End_temp = $_.end

#When on second pass, add the two sizes together
$PSTSize_temp = $PSTSize + $_.Size
Write-host “PST Size = $PSTSize_Temp” -ForegroundColor Cyan

If($PSTSize_temp -lt $SizeForPST){
“..Less than $SizeForPST  ($PSTSize_Temp)”
$PSTSize = $PSTSize_Temp
$PST_Start = $PST_Start_Temp

“….New DateRange:  $PST_Start — $PST_END”

}

If($PSTSize_Temp -gt $SizeForPST){
“..Greater Than $SizeForPST ($PSTSize_temp)”
$ServerObj = New-Object PSObject
$ServerObj | Add-Member NoteProperty -Name “Alias” -Value $mbx
$ServerObj | Add-Member NoteProperty -Name “Start” -Value $PST_Start
$ServerObj | Add-Member NoteProperty -Name “End” -Value $PST_End
$ServerObj | Add-Member NoteProperty -Name “Size” -Value $PSTSize
$PST_Final += $ServerObj

$PST_END = $PST_Start
$PSTSize = [int]0

}

}

}

$ServerObj = New-Object PSObject
$ServerObj | Add-Member NoteProperty -Name “Alias” -Value $mbx
#$ServerObj | Add-Member NoteProperty -Name “Start” -Value $PST_Start
$ServerObj | Add-Member NoteProperty -Name “Start” -Value “1/1/1900”
$ServerObj | Add-Member NoteProperty -Name “End” -Value $PST_End
$ServerObj | Add-Member NoteProperty -Name “Size” -Value $PSTSize
$PST_Final += $ServerObj

$PST_FINAL

}

If($PST_Final -eq $null){
$ServerObj = New-Object PSObject
$ServerObj | Add-Member NoteProperty -Name “Alias” -Value $mbx
#$ServerObj | Add-Member NoteProperty -Name “Start” -Value $PST_Start
$ServerObj | Add-Member NoteProperty -Name “Start” -Value “1/1/1900”
$ServerObj | Add-Member NoteProperty -Name “End” -Value $(get-date -Format “M/d/yyyy”)
$ServerObj | Add-Member NoteProperty -Name “Size” -Value $TotalSize
$PST_Final += $ServerObj

}

#Export the PST for this user
If($CutThePST -eq “Yes”){

$PST_Final | %{
$N = $_.Alias
$S = $_.Start
$E = $_.End
$PSTName = $N+”_”+$S+”_”+$E
$PSTName = $PSTName -replace (“/”,”-“)
$F = “$PSTFilePath$PstName.pst”
New-mailboxExportRequest -mailbox $N -contentfilter {(Received -ge $s) -and (Received -le $e)} -name $PSTName -filepath $F -baditemlimit 1000000 -acceptlargedataloss
}
}

Scenario: When adding a new mail profile, it automatically completes and does not display the Add Mail wizard for a different account

Scenario:  You want to add a new mail profile for a different account in Outlook. When adding the new mail profile (via Control Panel OR via “Choose Profile” when opening Outlook),  the new profile automatically completes and adds YOUR email account, and it doesn’t display the “Add Account” wizard where you can enter in a different email account.

Cause:  This is by design in Outlook 2016 through the use of ZeroConfigExchange.

Workarounds:

If you wish to disable this feature all together:

Navigate to one of the follow keys: 

HKey_Current_usersoftwareMicrosoftOffice16.0outlookautodiscover 

or

HKEY_CURRENT_USERSoftwarePoliciesMicrosoftOffice16.0OutlookAutoDiscover

Disable the ZeroConfigExchange DWORD by setting the value to 0

If you wish to keep this feature enabled but allow the wizard to pop up for additional accounts, you can leave the ZeroConfigExchange DWORD enabled (meaning it has a value of 1) and you can add the DWORD ZeroConfigExchangeOnce

Navigate to: HKEY_CURRENT_USERSoftwarePoliciesMicrosoftOffice16.0OutlookAutoDiscover

Add ZeroConfigExchangeOnce and set the value to 1

 

 

 

 

 

 

 

 

 

PowerShell: Find and copy text from multiple files

Scenario: You have to find a specific string of text in multiple files.  We are going to search for the term “steve” against all RPC Client Access logs for today.

Script:

#Variables

$files = get-childitem -path “\ExSrv1c$Program FilesMicrosoftExchange ServerV15LoggingRPC Client Access”
$result = @()
$term = “steve”

#Perform the Search

$files | Where CreationTime -gt 9/6/2018| Sort CreationTime -Descending | %{
       $F = $_.fullname
       $result += select-string $f -pattern $term | %{$_.Line}
       }

#Export Results

$result | out-file C:tempresults.txt

Error “Get-adgroupmember : The size limit for this request was exceeded”

Scenario:  You are trying to pull all members of a group into a variable and you receive this error:

Get-adgroupmember : The size limit for this request was exceeded

Solution:  Since the AD Group has a lot of members in it, running the command doesn’t work since it hit a PowerShell/AD Threshold.  Instead, run pull the members like this:

$group =[adsi]”LDAP://CN=ProdUsers,OU=Groups,DC=XYZ,DC=com”

$members = $group.psbase.invoke(“Members”) | foreach {$_.GetType().InvokeMember(“name”,’GetProperty’,$null,$_,$null)}

To display the results:

$members

$members.count

 

Use Search-Mailbox to perform a email item count based on specific words for multiple mailboxes

Scenario:  You are asked the question if you can provide a count of email items based on certain words per mailbox.

You sure can! I will give you a CLUE

We are going to look in each mailbox (the $mbx variable) for any email items that contain each word (in the $word variable).

Specifically, since I think it was Colonel Mustard, I will add his name as a AND statement in the KQL Search.  Because this game can be complex, I  will also include a NEAR statement to add a little of that complexity to the search-mailbox searchquery-KQL language.

Scriptlet:

#Variables
$mbx = “ProfessorPlum”, “MrsPeacock”, “MissScarlet”
$Word = “Candlestick”,  “Rope”,  “Wrench”
$word += “Ballroom NEAR(2) inside”, “Study NEAR(2) inside”, “Dining NEAR(2) Room”
$Result = @()

#Loop it
$mbx | %{
    $n = $_
    #Loop the Word
    $Word | %{
          $w = “$_”
          “Searching $n for word: $W”
          #Perform Serach
          $1 = (Search-Mailbox $n -SearchDumpster -SearchQuery “(Mustard) AND ($W)”  -EstimateResultOnly).ResultItemscount
         
          #Add to Table
          $returnobj = new-object psobject
          $returnobj |Add-Member -MemberType NoteProperty -Name “Name” -Value $n
          $returnobj |Add-Member -MemberType NoteProperty -Name “Word” -Value “(Mandava) AND ($W)” 
          $returnobj |Add-Member -MemberType NoteProperty -Name “EmailItemCount” -Value “$1” 
          $Result += $returnObj
         
          $w = $Null
          $1 = $null
          }
    $n = $null
    }

To view or Export:

$result

$result | Export-csv C:tempresult.csv -notypeinformation

Determine if a user has Elevated/Administrative Privileges in Exchange via PowerShell

Scenario: You want to clean up rights in Exchange, but you want to verify if a specific account has any elevated/administrative Exchange privilege.

Solution: Run the following command line via Exchange PowerShell:

get-managementroleassignment -roleassignee  steve

The results will give you all permissions via role in Exchange.  If its a standard mailbox with no elevated permissions, you will see a bunch of Default Roles for the RoleAssigneeName.  Any elevated privilege will also be listed as well.

Excel Vlookup: Add a Value from a different column when finding matched content in Excel

Scenario:  Lets say you have two different tables of data in Excel.  You want to not only compare the values, but also bring over additional values from different columns if there is a match.

Example:  Lets say you have the following Excel data on Sheet1; Names in Column A and a Numerical Value in Column B.

Sheet1:
1|A    | B
2|Steve| 1
3|Pat  | 4
4|Tom  | 3

And on Sheet2, you want to Compare the name values in the A column on Sheet1 with the name values on Sheet2.  If there is a match, you want to fill in the B column on Sheet2 with the numerical value from the B column on Sheet1.

Sheet2:
1|A    | B
2|Steve| ?
3|Pat  | ?
4|Tom  | ?

Solution:  In cell B2 on Sheet2, use VLOOKUP:

=vlookup(A2, Sheet1!$A$1:$B$4, 2, False)

The Arguments for the vlookup are broken like this:

A2 =  The value you want to compare on Sheet2
Sheet1!$A$1:$B$4    =    The range you want to compare the value to
 2 =  The Column index for which value should be returned based on a Match
False = Find an Exact Match

EWS Script: Reporting email items in the DiscoveryHolds folder located in RecoverableItems

Scenario:  You need to export a list of email items that are located in the DiscoveryHolds folder located in the RecoverableItems (Dumpster) of a Exchange Mailbox.  The report should include basic messaging information along with the Recipient email addresses. Note: If the message is a Draft (the isdraft property in the report) it may not have any of the common messaging information.

 

Scriptlet: Edit the #Variables.

#Finding items DiscoveryHolds in the RecoverableItems
#Variables
$cred = Get-credential  #credentials will fullaccess to access the mailbox
$mailboxname = “steve@domain.com”  #The Mailbox you wish to perform the query and restore on
$EWS_DLL = “C:Program FilesMicrosoftExchange ServerV15BinMicrosoft.Exchange.WebServices.dll”
$EWS_URL = “https://<your Exchange DNS namespace>/ews/exchange.asmx”
[datetime]$StartDate  = “6/26/2018” #Used for the LastModifiedTime
[datetime]$EndDate = “6/29/2018” #Used for the LastModifiedTime

#Configure connection to EWS
Import-Module -Name $EWS_DLL
$service = new-object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.Exchangeversion]::exchange2013)
$service.Url = new-object System.Uri($EWS_URL)
$service.UseDefaultCredentials = $false
$service.Credentials = $cred.GetNetworkCredential()

#Bind to the RootFolder
#$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::RecoverableItemsDeletions,$mailboxname)
#$Folder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
#$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$mailboxname)
#$Folder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)

$MailboxRootid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::RecoverableItemsRoot,$MailboxName)
$MailboxRoot=[Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$MailboxRootid)
#Find all folders in the mailbox
$FolderList = new-object Microsoft.Exchange.WebServices.Data.FolderView(1000)
$FolderList.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep
$findFolderResults = $MailboxRoot.FindFolders($FolderList)
$folder = $findFolderResults | Where displayname -like “DiscoveryHolds”

#Find Results for your Content Collection Filter
$sfCollection = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::And);
$Sfgt = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsGreaterThan([Microsoft.Exchange.WebServices.Data.ItemSchema]::LastModifiedTime, $StartDate)
$Sflt = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsLessThan([Microsoft.Exchange.WebServices.Data.ItemSchema]::LastModifiedTime, $EndDate)
$sfCollection.add($Sfgt)
$sfCollection.add($Sflt)
$view = new-object Microsoft.Exchange.WebServices.Data.ItemView(2000000)
$frFolderResult = $Folder.FindItems($sfCollection,$view)

#Enters the Recipient information into the $FrFolderResult Dataset
$ItemPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
if ($frFolderResult.Items.Count -gt 0) {
[Void]$service.LoadPropertiesForItems($frFolderResult , $ItemPropset)
}

#Exporting with your Results
$frFolderResult | Select from,Sender,IsDraft,{$_.toRecipients},{$_.CCRecipients},{$_.BCCRecipients},Subject,DateTimeReceived,LastModifiedTime | Export-csv c:tempresult.csv

 

Error: “You cant use the domain because it’s not an accepted domain for your organization” when onboarding a mailbox to Exchange Online

Scenario:  You receive the following error message when you attempt to onboard a mailbox from Exchange On-Premises to Exchange Online:

“You cant use the domain because it’s not an accepted domain for your organization”

Solution:  Check their Email aliases (Get-mailbox steve | Select -expandproperty emailaddresses). The user account may have an invalid email alias OR an alias that is not an accepted domain for the Exchange Online tenant.  For Example,  the On-Prem mailbox may have an alias of  steve@xyz.com  but the @xyz.com is not an accepted domain in the cloud.  Either add that domain to the cloud OR remove the alias.

Disconnect Terminal Sessions via PowerShell on Remote ServersComputers

Scenario:  You want to disconnect all remote sessions on ServerComputers via PowerShell remotely.

Script:

#Disconnect Terminal Sessions via PowerShell################

#Gather the Servers in the $Servers Variable

$Servers = get-exchangeserver Exch* | Select -expandproperty Name

#Gather the Sessions
$TS = $Servers | %{
$computer = $_
quser /server:$computer 2>&1 | Select-Object -Skip 1 | ForEach-Object {
                $CurrentLine = $_.Trim() -Replace ‘s+’,’ ‘ -Split ‘s’
                $HashProps = @{
                    UserName = $CurrentLine[0]
                    ComputerName = $Computer
                }

                # If session is disconnected different fields will be selected
                if ($CurrentLine[2] -eq ‘Disc’) {
                        $HashProps.SessionName = $null
                        $HashProps.Id = $CurrentLine[1]
                        $HashProps.State = $CurrentLine[2]
                        $HashProps.IdleTime = $CurrentLine[3]
                        $HashProps.LogonTime = $CurrentLine[4..6] -join ‘ ‘
                        $HashProps.LogonTime = $CurrentLine[4..($CurrentLine.GetUpperBound(0))] -join ‘ ‘
                } else {
                        $HashProps.SessionName = $CurrentLine[1]
                        $HashProps.Id = $CurrentLine[2]
                        $HashProps.State = $CurrentLine[3]
                        $HashProps.IdleTime = $CurrentLine[4]
                        $HashProps.LogonTime = $CurrentLine[5..($CurrentLine.GetUpperBound(0))] -join ‘ ‘
                }

                New-Object -TypeName PSCustomObject -Property $HashProps | Select-Object -Property UserName,ComputerName,SessionName,Id,State,IdleTime,LogonTime,Error
                }

}

#Disconnect the Sessions
$TS | %{
    $id = $_.id
    $Name = $_.computerName
    $User = $_.Username
    rwinsta $ID /server:$name
    Write-Host “Session $Id for $User on $Name successfully disconnected”
}
#######################################################