Automating Exchange Autodiscover Configuration During Server Installations

I recently came across a brilliant PowerShell script from Jeff Guillet (MCSM | MVP) that solves a problem many Exchange administrators know all too well — incorrect Autodiscover configurations being pushed to clients after a new server installation.
This issue often leads to certificate errors, Outlook connection issues, and unnecessary troubleshooting. Jeff’s script automates the entire process, ensuring a seamless experience for both users and admins.

The Challenge

When deploying new Microsoft Exchange servers, it’s easy for clients to receive outdated or incorrect Autodiscover URLs. This happens because new servers may register default service connection points (SCPs) before administrators have a chance to manually configure them.
As a result, Outlook clients can encounter certificate warnings or connect to the wrong Exchange instance — especially in coexistence or migration environments.

About the Script

Jeff Guillet’s PowerShell script automatically handles this configuration work for you. It monitors Active Directory and updates all relevant settings as soon as a new Exchange server is detected. This ensures your environment stays consistent and users never experience Autodiscover-related issues.

When a new server is installed, the script:

  • Updates the Autodiscover Service Connection Point (SCP) in Active Directory.
  • Configures the Outlook Anywhere (RPC over HTTP) FQDNs.
  • Sets the correct virtual directory URLs (EWS, OAB, ECP, MAPI, etc.) based on your preferred domain names.

This automation prevents Autodiscover from pointing to the wrong server and eliminates common certificate mismatch errors.

Why It Matters

Maintaining consistent Autodiscover and virtual directory configurations across all Exchange servers is essential for a stable environment. With this script in place, administrators can:

  • Prevent Outlook certificate pop-ups and connection failures.
  • Automate repetitive post-installation tasks.
  • Simplify coexistence during Exchange upgrades or migrations.
  • Ensure a reliable user experience with minimal manual intervention.

Real-World Benefits

For organizations managing hybrid or multi-server Exchange environments, Jeff’s script is a huge time-saver. It ensures every newly installed Exchange server immediately conforms to your organization’s configuration standards — all without lifting a finger.

By automating these critical steps, you reduce human error, improve reliability, and maintain consistent connectivity across the board.

In summary:
This PowerShell script by Jeff Guillet (MCSM | MVP) automatically updates the Autodiscover SCP, Outlook Anywhere FQDNs, and Exchange virtual directory URLs as new servers are deployed — helping administrators avoid certificate errors and streamline their deployment workflow.

For more information or to download the script, visit Jeff’s blog at www.expta.com or contact him directly at [email protected] / @expta on Twitter.

<#
.SYNOPSIS
Sets the Autodiscover service connection point (SCP) in Active Directory, Outlook Anywhere FQDNs, and virtual directory URLs for new Exchange servers as they are being installed.

Author/Copyright:    Jeff Guillet, MCSM | MVP - All rights reserved
Email/Blog/Twitter:  [email protected] | www.expta.com | @expta

THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.

.NOTES
Version 1.0, October 7, 2015
Version 2.0, July 22, 2016

Revision History
---------------------------------------------------------------------
1.0	Initial release
1.1	Updated online link; Added code to install RSAT-AD-PowerShell if required
2.0	Major update:
		Made setting the new values easier by cloning an existing server
		Now also configures Outlook Anywhere and Exchange virtual directory internal and external URLs
		Revised verbiage and use *-ClientAccessService cmdlets for Exchange 2016
		Added -Verbose output to display the values we're configuring
		Improved overall display output
	
.DESCRIPTION
Sets the Autodiscover service connection point (SCP) in Active Directory, Outlook Anywhere FQDNs, and virtual directory URLs for new Exchange servers as they are being installed.

Exchange setup always configures the new SCP with the FQDN of the server which throws certificate warnings in Outlook because the self-signed Exchange certificate is not trusted. Read https://blogs.technet.microsoft.com/exchange/2015/11/18/exchange-active-directory-deployment-site for more information about this behavior.

This script should be run from an existing Exchange server of the same version, and is designed to be run while the new Exchange server is being installed. It loops until it finds an existing SCP for the target server and then configures it to match the same SCP and virtual directory URL values from the server to clone.

.PARAMETER Server
Specifies the Exchange 2010/2013/2016 server to configure.

.PARAMETER ServerToClone
Specifies the Exchange 2010/2013/2016 server to use for reference. The SCP, Outlook Anywhere, and internal/external URL values will be copied from this server to the target server.

.PARAMETER DomainController
Query and set on the specified domain controller, otherwise uses the current binding DC.

.LINK
http://www.expta.com/2016/07/new-set-autodiscoverscp-v2-script-is-on.html

.EXAMPLE
PS C:\>Set-AutodiscoverSCP.ps1 -Server exch02 -ServerToClone exch01

This command continually queries the current configuration domain controller until it finds an SCP for server EXCH02 and then sets it to match the SCP of EXCH01. It also configures Outlook Anywhere and the internal/external virtual directory URLs to match those found on EXCH01.

.EXAMPLE
PS C:\>Set-AutodiscoverSCP.ps1 -Server exch02 -ServerToClone exch01 -DomainController dc03

This command is almost the same as the command in the previous example, except it continually queries DC03 for the SCP record and configures it on that domain controller. This is useful when configuring a new Exchange server in a different Active Directory site.
#>

# Define the script parameters
Param (
	[CmdletBinding()]
	[Parameter(Position=1,Mandatory=$true)]
	[string]$Server,
	[Parameter(Position=2,Mandatory=$true)]
	[string]$ServerToClone,
	[Parameter(Position=3,Mandatory=$false)]
	[string]$DomainController
)

Process {
	# Validate the target server
	$ErrorActionPreference = "SilentlyContinue"
	$Server = $Server.ToUpper()
	$Ping = New-Object System.Net.NetworkInformation.Ping
	$Reply = $Ping.Send($Server).Status
	if ($Reply -ne "Success") {
		Write-Host "ERROR: $Server is not online or is not a valid server name." -Foreground Red
		Exit(1)
	}

	# Validate the server to clone
	$ServerToClone = $ServerToClone.ToUpper()
	$Reply = $null
	$Reply = $Ping.Send($ServerToClone).Status
	if ($Reply -ne "Success") {
		Write-Host "ERROR: $ServerToClone is not online or is not a valid server name." -Foreground Red
		Exit(1)
	}

	# Select the Domain Controller to run against
	if ($DomainController) { 
		if ((Get-WindowsFeature RSAT-AD-PowerShell).InstallState -eq "Available") {
			Add-WindowsFeature RSAT-AD-PowerShell
		}
		Import-Module ActiveDirectory
		$Error.Clear()
		$DomainController = (Get-ADDomainController $DomainController).HostName
		$DomainController = $DomainController.ToUpper()
		If ($Error) { 
			Write-Host "ERROR: $DomainController is not online or is not a valid domain controller." -Foreground Red
			Exit(1)
		}
	}
	else {
		$DomainController = (Get-ADServerSettings).DefaultConfigurationDomainController.Domain
	}

	# Discover where the PSSession is established and show Exchange version warning
	$PSSessionServer = Get-ExchangeServer (Get-PSSession | Where-Object {$_.State -eq 'Opened'}).ComputerName
	if ($PSSessionServer.AdminDisplayVersion.Major -eq 14) { $ExVersion = "2010" }
	if ($PSSessionServer.AdminDisplayVersion.Major -eq 15 -and $PSSessionServer.AdminDisplayVersion.Minor -eq 0) { $ExVersion = "2013" }
	if ($PSSessionServer.AdminDisplayVersion.Major -eq 15 -and $PSSessionServer.AdminDisplayVersion.Minor -eq 1) { $ExVersion = "2016" }
	if ($ExVersion -eq $null) {
		Write-Host "ERROR: This script must be run from the Exchange Management Shell on an Exchange 2010-2016 server." -Foreground Red
		Exit(1)
	}
	elseif ($ExVersion -eq "2010") {
		Write-Host "This script is currently running in an Exchange 2010 PowerShell session. Make sure $Server is installing Exchange 2010." -Foreground White -BackGround Red
		if ((Get-OwaVirtualDirectory -Server $ServerToClone -DomainController $DomainController -ADPropertiesOnly).InternalUrl.AbsoluteUri -eq $null) {
			Write-Host "ERROR: $ServerToClone is a higher version of Exchange than this server." -Foreground Red
			Exit(1)
		}
		Write-Host "NOTE: If you're installing your first Exchange 2013/2016 server in this environment you should run this script from that server while setup is running."
		Write-Host
	}
	else {
		Write-Host "This script is currently running in an Exchange $ExVersion PowerShell session. Make sure $Server is installing Exchange 2013 or later." -Foreground White -BackGround Red
		Write-Host
	}
	$ErrorActionPreference = "Continue"

	# Get the SCP, Outlook Anywhere, and virtual directory URL values from $ServerToClone
	Write-Host -NoNewline "Gathering SCP, Outlook Anywhere, and Exchange virtual directory values from $ServerToClone... " -Foreground White
	$SCPValue = (Get-ClientAccessServer $ServerToClone -DomainController $DomainController -ErrorAction SilentlyContinue -WarningAction SilentlyContinue).AutoDiscoverServiceInternalUri.AbsoluteUri
	$EasInternal = (Get-ActiveSyncVirtualDirectory -Server $ServerToClone -DomainController $DomainController -ADPropertiesOnly).InternalUrl.AbsoluteUri
	$EasExternal = (Get-ActiveSyncVirtualDirectory -Server $ServerToClone -DomainController $DomainController -ADPropertiesOnly).ExternalUrl.AbsoluteUri
	$EcpInternal = (Get-EcpVirtualDirectory -Server $ServerToClone -DomainController $DomainController -ADPropertiesOnly).InternalUrl.AbsoluteUri
	$EcpExternal = (Get-EcpVirtualDirectory -Server $ServerToClone -DomainController $DomainController -ADPropertiesOnly).ExternalUrl.AbsoluteUri
	If ($ExVersion -ne "2010") {
		$MapiInternal = (Get-MapiVirtualDirectory -Server $ServerToClone -DomainController $DomainController -ADPropertiesOnly).InternalUrl.AbsoluteUri
		$MapiExternal = (Get-MapiVirtualDirectory -Server $ServerToClone -DomainController $DomainController -ADPropertiesOnly).ExternalUrl.AbsoluteUri
	}
	$OabInternal = (Get-OabVirtualDirectory -Server $ServerToClone -DomainController $DomainController -ADPropertiesOnly).InternalUrl.AbsoluteUri
	$OabExternal = (Get-OabVirtualDirectory -Server $ServerToClone -DomainController $DomainController -ADPropertiesOnly).ExternalUrl.AbsoluteUri
	$OwaInternal = (Get-OwaVirtualDirectory -Server $ServerToClone -DomainController $DomainController -ADPropertiesOnly).InternalUrl.AbsoluteUri
	$OwaExternal = (Get-OwaVirtualDirectory -Server $ServerToClone -DomainController $DomainController -ADPropertiesOnly).ExternalUrl.AbsoluteUri
	$EwsInternal = (Get-WebServicesVirtualDirectory -Server $ServerToClone -DomainController $DomainController -ADPropertiesOnly).InternalUrl.AbsoluteUri
	$EwsExternal = (Get-WebServicesVirtualDirectory -Server $ServerToClone -DomainController $DomainController -ADPropertiesOnly).ExternalUrl.AbsoluteUri
	$OaInternal = (Get-OutlookAnywhere -Server $ServerToClone -DomainController $DomainController -AdPropertiesOnly).InternalHostname.HostnameString
	$OaExternal = (Get-OutlookAnywhere -Server $ServerToClone -DomainController $DomainController -AdPropertiesOnly).ExternalHostname.HostnameString
	Write-Host "Done!" -Foreground White

	# Verbose output shows cloned values
	Write-Verbose "SCP -  $SCPValue"
	Write-Verbose "EAS -  $EasInternal | $EasExternal"
	Write-Verbose "ECP -  $EcpInternal | $EcpExternal"
	Write-Verbose "MAPI - $MapiInternal | $MapiExternal"
	Write-Verbose "OAB -  $OabInternal | $OabExternal"
	Write-Verbose "OWA -  $OwaInternal | $OwaExternal"
	Write-Verbose "EWS -  $EwsInternal | $EwsExternal"
	Write-Verbose "OA -   $OaInternal | $OaExternal"

	# Check if we're running this script from the target server
	if ([System.Net.Dns]::GetHostByName($Server).HostName -eq [System.Net.Dns]::GetHostByName(($env:computerName)).HostName) {
		Write-Host
		Write-Host "NOTE: You are running this script from the same server you're configuring. If you're running it while installing Exchange the script may stall during configuration since setup restarts IIS and web services several times. If that happens simply CTRL-C and restart the script." -Foreground Yellow
		Write-Host
	}

	# Continually query AD for SCP value for $Server
	do {
		if ($ExVersion -eq "2016") {
			$SCP = (Get-ClientAccessService $Server -DomainController $DomainController -ErrorAction SilentlyContinue).AutoDiscoverServiceInternalUri.AbsoluteUri
		}
		else {
			$SCP = (Get-ClientAccessServer $Server -DomainController $DomainController -ErrorAction SilentlyContinue).AutoDiscoverServiceInternalUri.AbsoluteUri
		}
		$PercentComplete++
		if ($PercentComplete -eq 101) { $PercentComplete = 1 }
		Write-Progress -Activity "Searching for the SCP value for Exchange server $Server in Active Directory..." -PercentComplete $PercentComplete -Status "Please wait."
	}
	until ($SCP -ne $null)

	# Set the new SCP value in Active Directory
	$Error.Clear()
	if ($ExVersion -eq "2016") {
		Set-ClientAccessService $Server -AutoDiscoverServiceInternalUri $SCPValue -DomainController $DomainController
	}
	else {
		Set-ClientAccessServer $Server -AutoDiscoverServiceInternalUri $SCPValue -DomainController $DomainController
	}
	If ($Error) { Exit(1) }
	Write-Host "Setting SCP value for $Server to $SCPValue" -Foreground Green

	# Set the internal and external URLs for all virtual directories
	Write-Host -NoNewLine "Setting ActiveSyncVirtualDirectory internal and external URLs... " -Foreground Cyan
	Get-ActiveSyncVirtualDirectory -Server $Server -DomainController $DomainController -ADPropertiesOnly | Set-ActiveSyncVirtualDirectory -InternalUrl $EasInternal -ExternalUrl $EasExternal -DomainController $DomainController -WarningAction SilentlyContinue
	Write-Host "Done!" -Foreground Cyan
	Write-Host -NoNewLine "Setting EcpVirtualDirectory internal and external URLs... " -Foreground Cyan
	Get-EcpVirtualDirectory -Server $Server -DomainController $DomainController -ADPropertiesOnly | Set-EcpVirtualDirectory -InternalUrl $EcpInternal -ExternalUrl $EcpExternal -DomainController $DomainController -WarningAction SilentlyContinue
	Write-Host "Done!" -Foreground Cyan
	If ($MapiInternal -ne $null) {
		Write-Host -NoNewLine "Setting MapiVirtualDirectory internal and external URLs... " -Foreground Cyan
		Get-MapiVirtualDirectory -Server $Server -DomainController $DomainController -ADPropertiesOnly | Set-MapiVirtualDirectory -InternalUrl $MapiInternal -ExternalUrl $MapiExternal -DomainController $DomainController -WarningAction SilentlyContinue
		Write-Host "Done!" -Foreground Cyan
	}
	Write-Host -NoNewLine "Setting OabVirtualDirectory internal and external URLs... " -Foreground Cyan
	Get-OabVirtualDirectory -Server $Server -DomainController $DomainController -ADPropertiesOnly | Set-OabVirtualDirectory -InternalUrl $OabInternal -ExternalUrl $OabExternal -DomainController $DomainController -WarningAction SilentlyContinue
	Write-Host "Done!" -Foreground Cyan
	Write-Host -NoNewLine "Setting OwaVirtualDirectory internal and external URLs... " -Foreground Cyan
	Get-OwaVirtualDirectory -Server $Server -DomainController $DomainController -ADPropertiesOnly | Set-OwaVirtualDirectory -InternalUrl $OwaInternal -ExternalUrl $OwaExternal -DomainController $DomainController -WarningAction SilentlyContinue
	Write-Host "Done!" -Foreground Cyan
	Write-Host -NoNewLine "Setting WebServicesVirtualDirectory internal and external URLs... " -Foreground Cyan
	Get-WebServicesVirtualDirectory -Server $Server -DomainController $DomainController -ADPropertiesOnly | Set-WebServicesVirtualDirectory -InternalUrl $EwsInternal -ExternalUrl $EwsExternal -DomainController $DomainController -WarningAction SilentlyContinue
	Write-Host "Done!" -Foreground Cyan
	Write-Host -NoNewLine "Setting Outlook Anywhere FQDNs... " -Foreground White
	$OA = Get-OutlookAnywhere -Server $Server -DomainController $DomainController -AdPropertiesOnly
	If ($ExVersion -ne "2010") {
		$OA | Set-OutlookAnywhere -InternalHostname $OaInternal -InternalClientsRequireSsl $OA.InternalClientsRequireSsl -InternalClientAuthenticationMethod $OA.InternalClientAuthenticationMethod -ExternalHostname $OaExternal -ExternalClientsRequireSsl $OA.ExternalClientsRequireSsl -ExternalClientAuthenticationMethod $OA.ExternalClientAuthenticationMethod -DomainController $DomainController -WarningAction SilentlyContinue
	}
	else {
		$OA | Set-OutlookAnywhere -ExternalHostname $OAExternal -DomainController $DomainController -WarningAction SilentlyContinue
	}
	Write-Host "Done!" -Foreground White
	Write-Host
	Write-Host "Be sure to install and enable the same trusted SSL certificate that's configured on $Server to complete configuration." -Foreground Red
}

Leave a Reply

Your email address will not be published. Required fields are marked *