Check for Autodiscover ServiceBindingInformation during the installation of Exchange — This way you can be alerted when the property is configured, and we can $Null it out.

Scenario: We are installing Exchange On-Premises on some new servers, and we want to minimize the amount of Security Alerts that pop up on users Outlook connections. The Security Alerts pop up because we do not put the servername on the public certificates AND Exchange wants to configure the autodiscover record with https://<local server name>/autodiscover/autodiscover.xml — silly Microsoft.

Solution: During the Exchange Installation, run the following scriptlet to loop through and beep/display when it is there, so you can go in and null it out in ADSI edit (or Exchange if it allows you).

#Variables

You can find the exact $AD property by locating the DN of the server by navigating through the ADSIEdit Configuration (working your way backwards from the AD property below) and copying it.

$s = "ExServer21"
$ad = "CN=$s,CN=Autodiscover,CN=Protocols,CN=$S,CN=Servers,CN=Exchange Administrative Group,CN=Administrative Groups,CN=Enterprise Exchange,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=domain,DC=com"

#Loop It

Do{
$1 = Get-ADObject $AD -properties * |Select -expandproperty ServiceBindingInformation
"Checking $s for Service Binding Information...."
If($1 -ne $null){ 
    [console]::beep(500,300)
    "ServiceBindingInfomation: $1"}
Sleep 5
}While($C -ne "RunForever")

You could also run set-clientaccessserver <servername> -AutodiscoverServiceInternalUri $null



Advertisement

Scriptlets to check Recent Windows and Exchange updates/patches

Scenario: You have just updated your Exchange Servers (or Windows Servers) and you want to verify the successful install of the patches through logic.

Solution: SCRIPTLETS:

#Pull a list of Servers
   $Servers = get-exchangeserver |sort name| Select -ExpandProperty Name 
   #-or-
   $servers = "ExServer1","ExServer2"
    
#Pull Updates
   $Ex_Updates = @(); $servers | %{$n = $_; "Checking Exchange File for $n"; $Ex_Updates+= Invoke-Command -ComputerName $n -ScriptBlock {GCM exsetup |%{$_.Fileversioninfo}}}
   
   $Win_Updates = @(); $servers | %{$n = $_; "Checking Windows Updates for $n"; $Win_Updates+= Get-HotFix -ComputerName $n |Where InstalledOn -ge (get-date).AddDays(-7) | Select @{Name="Server";Expression={$n}}, Description, HotFixID, InstalledOn }
    
#Display variables of updates
    $Ex_updates 
    $Win_Updates

    #Note: You can group to make it easier to quickly identify everything looks right
    $Win_Updates |Group Hotfixid
      
    $Win_Updates |Group server 

Windows – Unable to join domain. “An account with the same name exists in Active Directory. Re-using the account was blocked by security policy”

Scenario: The error below was received when joining a new computer to a domain (the computer object was pre-staged in AD by creating the computer object and resetting it).

Error: “An account with the same name exists in Active Directory. Re-using the account was blocked by security policy”

Solution: Add the following Reg Key:

Reg add HKLM\System\CurrentControlSet\Control\Lsa /v NetJoinLegacyAccountReuse /t REG_DWORD /d 1 /f

Error: 450 4.4.317 Cannot connect to remote server – 451 5.7.3 STARTTLS is required to send mail

Scenario: When attempting to send email from Exchange Online to Exchange On-Premises via Hybrid connectors, Exchange Online receives the following error message with a Defer Event: LED=450 4.4.317 Cannot connect to remote server [Message=451 5.7.3 STARTTLS is required to send mail]

This error means is that a certificate that supports TLS is present on the receiving (Exchange On-Premises) server, EXCEPT it cannot build a TLS connection because of an invalid configuration.

Solution: In our case, a new certificate that was recently installed on the Exchange On-Premises Server had a different TLS Certificate Name than what was configured on the Receive Connector. Setting the ‘TLSCertificateName’ on the receive connector with the NEW Certificate fixed the issue.

#Pull the Certificate
$Cert = Get-ExchangeCertificate -Thumbprint "<Thumbprint of server with SMTP Service>"

#Build the TLSCert String that will be used to compare and set
$TLSCert = (‘<I>’+$cert.issuer+'<S>’+$cert.subject)

#Check and compare the TLSCertificateName on the Receive Connector with $TLSCert
Get-ReceiveConnector "<Servername>\Default Frontend <Servername>" | Select TLSCertificateName
$TLSCert

#If the CertName is different, set the Receive Connector with $TLSCert
Get-ReceiveConnector "<Servername>\Default Frontend <Servername>" | Set-ReceiveConnector -TlsCertificateName $TLSCert

Renewing Certificate in a Hybrid Exchange On-Premises Environment (Concern of the SMTP Service)

Scenario: The third-party certificate is expiring and needs to be renewed on multiple Exchange On-Premises server that hosts IIS, SMTP, POP, and IMAP. There is concern about the hybrid components for successful SMTP Routing between Exchange On-Premises and Exchange Online since we are moving the SMTP Service to a different certificate.

Solution: The components below will walk you through creating the renewal and the components to look at:


1. Create a Cert request off of the expiring Cert Req (and then hand off to third party cert provider)

#Variable
$ExpiringCert = "<thumbprint of cert>"
$Req = "\\ExServer\c$\temp\cert\CertRenewal.req"
$Server = "ExServer"

#Run the command to get the .req
$txtrequest = Get-ExchangeCertificate -Thumbprint $ExpiringCert -server $Server | New-ExchangeCertificate -GenerateRequest -privatekeyexportable:$true

[System.IO.File]::WriteAllBytes($req, [System.Text.Encoding]::Unicode.GetBytes($txtrequest))

2. Complete the Pending Exchange Server Request (After downloading the cert from the third party vendor)

#Variable
$server = "ExServer"
$Renewed_Cert = "\\ExServer\c$\temp\cert\NewCert.crt"
$Renewed_Cert
Import-ExchangeCertificate -FileData ([System.IO.File]::ReadAllBytes($Renewed_Cert))  -Server $Server  -privatekeyexportable:$true 

3. Export the new cert (pfx) package so it contains the private and public key/components

#Variables
$c = "<new cert thumbprint"
$Password = "Batman"
$PFX =  "\\ExServer\c$\temp\cert\NewCert.pfx"
$server = "ExServer"



$cert = Export-ExchangeCertificate -Thumbprint $c -BinaryEncoded -Password (ConvertTo-SecureString -String $Password -AsPlainText -Force) -server $server
[System.IO.File]::WriteAllBytes($PFX, $cert.FileData)

4. Import the Certificate onto multiple servers

$ExchangeServers = get-exchangeserver
$Password = "Batman"
$ExchangeServers.name | %{
"Running on $_"
Import-ExchangeCertificate -FileData ([System.IO.File]::ReadAllBytes($PFX)) -Password (ConvertTo-SecureString -String $Password -AsPlainText -Force) -server $_
}

5. Moving Services

#Variables
      $New_Thumbprint = "<new cert thumbprint>"
      $ExchangeServers = Get-exchangeserver

 #Enable the IMAP,POP,IIS,and SMTP Services to the new cert
 $ExchangeServers.name | %{
     "Enabling Services on $_"
     Enable-Exchangecertificate -thumbprint $New_Thumbprint -server $_ -services IMAP,POP,IIS,SMTP -confirm:$false -force
}

6. Restarting Each Service

#Variables
$ExchangeServers = Get-exchangeserver      

#Restart the Services for IIS, IMAP, POP, and SMTP 
$ExchangeServers.name | %{
   "Restarting IIS on $_"
   iisreset $_

   "Restarting MSExchangeTransport on $_"
   Get-Service msexchangetransport -ComputerName $_ | Restart-service

   "Restarting IMAP and POP on $_"
   Get-Service msexchangeimap* -ComputerName $_ | Restart-service
   Get-Service msexchangepop* -ComputerName $_ | Restart-service
}

7. Check Outbound Connector in Exchange Online

#Notes: Our Subjectname didnt change, so we didnt have to do anything here

#Validate "Outbound_To_OnPremises" is configured with the subjectname in the Certificate/SAN

#Log into EAC --> Mail Flow --> Connectors --> Select the "<Outbound_To_OnPremises>" connector --> Edit "Security Restrictions" section if necessary.  The value should reflect a domain name or another identifiable piece of info in the new cert'.

8. Check Exchange On-Premises Receive Connectors

#Notes: We had to fix this one because the new $TLSCert was different than the TLSCertificateName on each Receive Connector; each connector still referenced the original cert.  Fixed by:  Set-ReceiveConnector "$_\Default Frontend $_" -tlsCertificateName $TLSCert

#Variables: 
$New_Thumbprint = "<new cert thumbprint>"
$New_Cert = Get-ExchangeCertificate -Thumbprint $New_Thumbprint -server "ExServer"
$TLSCert = (‘<I>’+$cert.issuer+'<S>’+$cert.subject)
$ReceiveConnectors = @()

#Check the TLSCertificateName on all Receive Connectors and compare to $TLSCert
$ExchangeServers.name | %{$ReceiveConnectors += Get-ReceiveConnector "$_\Default Frontend $_" | Select Identity, TLSCertificateName}

#To fix:    
$ExchangeServers.name | %{Set-ReceiveConnector "$_\Default Frontend $_" -tlsCertificateName $TLSCert}

9. Check the Exchange On-Premises Transport Service

#Notes: This should be done automatically

#Variables
$ExchangeServers = Get-exchangeserver   
$TransportService_Cert = @()

#Loop
#$ExchangeServers.name | %{$TransportService_Cert +=  get-transportservice $_  |Select Name,InternalTransportCertificateThumbprint}; $TransportService_Cert

10. Check the Exchange On-Premises Hybrid Send Connector “<Outbound to Office 365>”

#Notes: You may need to set this

#Variables
$SC = "Outbound to Office 365"
$New_Thumbprint = "new cert thumbprint"
$New_Cert = Get-ExchangeCertificate -Thumbprint $New_Thumbprint -server "ExServer"
$New_TLSCertificateName = (‘<I>’+$New_Cert.issuer+'<S>’+$New_cert.subject)

#Verify the TLSCertificateName on the SendConnector
$SendConnector_TLSCertificateName = Get-SendConnector -identity  $SC | Select TLSCertificateName

#Does the $SendConnector_TLSCertificateName match the NEW_TLSCertificateName? If Not, you may need to run the following:
Set-SendConnector "Outbound to Office 365"  -TlsCertificateName $New_TLSCertificateName

11. Check the ExOnline Inbound Connector “Inbound from …”

#Notes: We did need to fix this

#Variables
$New_Thumbprint = "<thumbprint of new cert>"
$New_Cert = Get-ExchangeCertificate -Thumbprint $New_Thumbprint -server "ExServer"
$New_TLSCertificateName = (‘<I>’+$New_Cert.issuer+'<S>’+$New_cert.subject)

#Set the Inbound Connector with the $New_TLSCertificateName value of the new Cert
  #Connect to Exchange Online PowerShell or the EAC GUI
  #If PowerSHell:
        $ExOnline_TLSSenderCertificateName = Get-InboundConnector "Inbound from ..." | Select TLSSenderCertificateName

#If GUI: Log into EAC --> Mail Flow --> Connectors --> Select the "Inbound from ..." connector --> Edit the "How to identify email sent from your email server" section. 


#To Fix:
#PowerShell: set-InboundConnector "Inbound from ..." -TlsSenderCertificateName $New_TLSCertificateName
#EAC:  Edit the "How to identify email sent from your email server" section with the #New_TLSCertificateNameValue

Configure a Distribution Group to send a reply message every time (Not an Out-of-Office Message)

Scenario: You need a distribution group to send a ‘specific’ reply message back to the sender, every time a sender sends to it. An Out-of-Office message will not do because it will only send a reply one time.

Note: Distribution Groups do not have this functionality, however there is a work around with using a shared mailbox.

Solution:
1. Create a Shared Mailbox that will host sending the reply messages. We will call it: ReplyMessage@steveman.com

2. Log into an Outlook client as the shared mailbox and create the following inbox rule:

“Apply this rule after the message arrives
have server reply using “blah blah blah


3. Even though this is a Inbox Rule, set your Distribution Group so it allows sending Out-of-Office (OOF) messages which is turned off by default. This is required for return messages:

Set-DistributionGroup DL01@steveman.com -SendOofMessageToOriginatorEnabled:$true

4. Create a Transport Rule OR add that shared mailbox to your distribution list. I chose the Transport Rule because it is cleaner to the end users — they wont start asking questions about this new account. We are using the AnyOfToCCHeader to trigger the rule if it the Distribution list is sent to.

New-TransportRule -name “BCC_Reply” -AnyOfToCCHeader DL01@steveman.com -blindcopyto ReplyMessage@steveman.com

Mailbox Dumpster (aka TotalDeletedItemSize aka RecoverableItems) hit the 100GB size quota due to a bloated recurring meeting

Scenario: You have a mailbox that is similar to the following scenario:

-On a Hold: A mailbox is protected by a hold such as a Litigation Hold, eDiscovery/In-Place Hold, SCC Compliance Hold, etc.

-Hit the Quota: The mailbox has either already hit, or is approaching, the mailbox dumpster 100GB quota

-Item Count to Size Ratio doesnt make sense, at first: When you run the following command you notice that there are very few items in the folder that is at or near the quota — it would seem that the item count and folder size doesn’t make sense for 200 items to equal 99GB. Command: get-mailboxfolderstatistics <name> -folderscope recoverable | Select Name, ItemsInFolder, FolderSize

-Symptoms experienced by the user: Users have a issue deleting email items OR may experience issues with calendaring events.

Investigation: We discovered that there is a long-lasting recurring meeting that the impacted user/mailbox was an attendee for. The organizer of the meeting would edit single occurrences of the meeting series to post meeting agendas and attachments (this is where the size bloat comes from). It appears there is a conflict with this user being on a hold, and retaining a collective buildup of all edited occurrences of the meeting series. There was around 200 individual meeting ocurrences of thise meeting series, and each item is now ballooned in size to just under 200MB each. This contributes to the size of the dumpster folder, and the overall dumpster quota.

Solution: There is a command that we can run that will cleanup all the duplicate calendar items in the dumpster, even when a mailbox is on a Hold:

start-managedfolderassistant <name> -holdcleanup.

However, in our scenario the email items lived in the Purges recoverable items folder and the command did not work in that folder. Using MFCMapi we had to do a Move (in MFCMapi language a Copy –> Paste with the move option selected) and put all those items into the Versions folder. When the messages were moved into the Versions folder, the command worked and within the hour all the duplicate messages that were ballooned in size were now gone AND the dumpster was well under quota.

Invoke-WebRequest : The underlying connection was closed: An unexpected error occurred on a send

Scenario:  You receive the following error when you attempt to Invoke-WebRequest from PowerShell:
Invoke-WebRequest : The underlying connection was closed: An unexpected error occurred on a send.

Solution: Run the following in PowerShell to make sure it uses TLS1.2

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12


GraphAPI – Move messages in a mailbox folder to another folder

Scenario: You want to move messages from one mailbox folder to another

Prerequisites: You already have the following configured:
Registered App in Azure: An Azure Registered app to connect to with Application Permissions for Mail.Read, Mail.ReadWrite, Mail.ReadBasic.All, and Mail.Send. (Not all permissions listed may be necessary for the specific function below)
Bearer Authentication Token: A method for pulling back a bearer token and storing it to pass Authorization into your RestAPI package (See my previous post about get-accesstoken). The get-accesstoken authenticates against your Registered App in Azure.

Scriptlets:

#1. Build $mbx
$mbx = "steve@steveman.com"

#2. Build your $AppURI of the source location of the mailbox folder items
#Multiple Examples:
$appuri = "https://graph.microsoft.com/v1.0/users/$mbx/mailfolders/inbox/Messages"   

$appuri = "https://graph.microsoft.com/v1.0/users/$mbx/mailfolders/AAMkADY3ODhkMDNlLTRjM2YtNDJjYi04YTkzLTFmNjFhYTcyMjA2NwAuAAAAAAAM5kRh6DylRZ4XsImGGVClAQA0fvQp6d6ET6JezCAd8xOWAAFrER8_AAA=/Messages"
                                
$appuri = "https://graph.microsoft.com/v1.0/users/$mbx/mailfolders/RecoverableItemsDiscoveryHolds/Messages"   
                
$appuri = "https://graph.microsoft.com/v1.0/users/$mbx/mailfolders('RecoverableItemsPurges')/Messages"


#3. Find All messages in that source folder
 $messages = @()
                Do{
                    "Running $appUri"
            
                    $RestSplat = @{
                        URI = $appuri
                        Headers = $(get-accesstoken)
                        Method = 'GET'
                        ContentType = "application/json"
                    }
                
                    $Tempresults = Invoke-RestMethod @RestSplat
                    $messages += $tempresults.value
                    $appuri = $tempresults."@odata.NextLink"
                    }While($appuri -ne $null)

                #Display your Messages if you want to see
                $Messages

#4. Move the messages you found in the step above to the destination folder
#Build a $Params Variable with the folderID we want to pass to
#You can use a Base64 ID
$params = @{
	                    DestinationId = "AAMkADY3ODhkMDNlLTRjM2YtNDJjYi04YTkzLTFmNjFhYTcyMjA2NwAuAAAAAAAM5kRh6DylRZ4XsImGGVClAQA0fvQp6d6ET6JezCAd8xOWAAFrER8_AAA="
                    } | Convertto-Json

#Or use the name of a common folder 
                    $params = @{
	                    DestinationId = "Inbox"
                    } | Convertto-Json

#Move the Messages
            $c = 0
            $T = $messages.id.count
            $messages.id | %{
                $m = $_
                $c++
                "$c / $t : Moving $M"
                $appuri = "https://graph.microsoft.com/v1.0/users/$mbx/messages/$m/move"
                        $RestSplat = @{ 
                            URI         = $appuri
                            Headers     = $(get-accesstoken)
                            Method      = 'POST' 
                            ContentType = "application/json" 
                            Body = $params 
                            }
                $move = Invoke-RestMethod @RestSplat 
            }

GraphAPI – Creating a Mailbox Folder via PowerShell

Scenario: You want to create mailbox folders using GraphAPI via PowerShell

Prerequisites: You already have the following configured:
Registered App in Azure: An Azure Registered app to connect to with Application Permissions for Mail.Read, Mail.ReadWrite, Mail.ReadBasic.All, and Mail.Send. (Not all permissions listed may be necessary for the specific function below)
Bearer Authentication Token: A method for pulling back a bearer token and storing it to pass Authorization into your RestAPI package (See my previous post about get-accesstoken). The get-accesstoken authenticates against your Registered App in Azure.

Scriptlets:

#1. Build $MBX
$mbx = "steve@steveman.com"

#2. Build your AppURI
$appuri = "https://graph.microsoft.com/v1.0/users/$mbx/mailfolders"

#3. Build your $params to include the new Folder Name and if it should be hidden
                $params = @{
        	         DisplayName = "TestGraphAPI"
	                 IsHidden = $false
                     } | ConvertTo-Json

#4. Build your RestSplat package
#Build your URI Package to POST
                $RestSplat = @{ 
                    URI         = $appuri
                    Headers     = $(get-accesstoken)
                    Method      = 'POST' 
                    ContentType = "application/json" 
                    Body = $params 
                }        

#5. Execute the RestSplat package
Invoke-RestMethod @RestSplat