Tuesday, April 18, 2017

Block all Outlook versions earlier than X version

For one reason or another, some organizations have the requirement to block older versions of Outlook from connecting to their Exchange environment. This can easily be done on a per-mailbox or on a per-mailbox server basis.

Let’s say we want to block user nuno from accessing his mailbox using all Outlook versions earlier than 11.8010.8036. To achieve this, we run the following cmdlet:
Set-CASMailbox nuno -MAPIBlockOutlookVersions "-11.8010.8036"

When the user tries to use an older version of Outlook, he will get the following message (in this case I blocked all versions of Outlook):

Followed by:

To restore access to the mailbox to any version of Outlook, we simply clear the MAPIBlockOutlookVersion parameter:
Set-CASMailbox nuno -MAPIBlockOutlookVersion $null

To achieve the same but on a per-server basis, we need to use a Registry Key on all servers. In the next example, we create the Disable MAPI Clients registry value to block access to all mailboxes for Outlook clients prior to version 14.0.0:
New-ItemProperty "HKLM:\System\CurrentControlSet\Services\MSExchangeIS\ParametersSystem" -Name "Disable MAPI Clients" -PropertyType String -Value "0.0.0-5.9.9, 14.0.0-"

Important: be careful when restricting client access because server-side Exchange components must also use MAPI to log on. Some components report their client version as the component name (such as SMTP or OLE DB), while others report the Exchange build number (such as 6.0.4712.0). For this reason, we must avoid restricting clients that have version numbers that start with 6.x.x.

Outlook Calendar Sharing Error Message

When you use Outlook to share a calendar with another user, you might get one of the following errors:
  • Calendar sharing is not available with the following entries because of permission settings on your network
  • You Cannot request to share calendars with the following people because of permission settings on your network


This typically happens when users type the recipients’ email address or use the address from their cached addresses. When this happens, clear the cached entry for the user, click the TO button and select the intended recipient(s) from the address book.

Sunday, April 9, 2017

How to Check Exchange Prepare AD Values

Before installing a new Exchange environment, and sometimes when updating to a newer Cumulative Update, we need to prepare our Active Directory (AD) forest and its domains. Exchange needs to prepare AD so that it can store information about users' mailboxes and the configuration of Exchange servers in the organization.

Once we have extended the AD Schema, prepared AD, and prepared all its domains, several properties are updated to show that preparation is complete. At this stage, it is strongly recommended to make sure everything has gone smoothly. To do so, typically a tool called Active Directory Service Interfaces Editor (ADSI Edit) is used to check these properties have been updated to the value they should have. Each property needs to match the value for the release of Exchange that we are installing:
  • “In the Schema naming context, verify that the rangeUpper property on ms-Exch-Schema-Verision-Pt is set to the value shown for your version of Exchange 2016 in the Exchange 2016 Active Directory versions table;
  • In the Default naming context, verify that the objectVersion property in the Microsoft Exchange System Objects container under DC=root domain is set to the value shown for your version of Exchange 2016 in the Exchange 2016 Active Directory versions table;
  • In the Configuration naming context, verify that the objectVersion property in the CN=your organization,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=domain container is set to the value shown for your version of Exchange 2016 in the Exchange 2016 Active Directory versions table.”
 
 
But there are other methods. For example, Michel de Rooij has blogged about checking these properties using LDAP and PowerShell in his Exchange Schema Versions blog post. Additionally, we can use the LDP.exe tool (an LDAP client GUI), or we can use DSQuery.
 
Dsquery is a command-line tool that is built into Windows Server when the Active Directory Domain Services (AD DS) server role or the AD DS admin tools are installed. Using dsquery we can query AD (shocker!) by using search criteria that we specify.
 
So, let’s say we want to check the value of the rangeUpper property using dsquery. We can easily do this by running the following command:
DSQUERY.exe * “CN=ms-Exch-Schema-Version-Pt,CN=schema,CN=configuration,DC=domain,DC=com” -Scope base -Attr rangeUpper

In my lab, where I have a nunomota.pt domain, I would run:
DSQUERY.exe * “CN=ms-Exch-Schema-Version-Pt,CN=schema,CN=configuration,DC=nunomota,DC=pt” -Scope base -Attr rangeUpper

To check for the other two properties, we would use the following two queries:
DSQUERY.exe * “CN=Exchange_Org_Name,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=domain,DC=com” -Scope base -Attr objectVersion

DSQUERY.exe * “CN=Exchange_Org_Name,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=domain,DC=com” -Scope base -Attr msExchProductId

Now that we know how to easily check these properties, we can make it even easier by automating the check with the script below. In order to automate this, I use the Get-OrganizationConfig Exchange cmdlet to determine the Exchange Organization Name (when it is not specified), so the Exchange Management Shell (EMS) should be used. This means that if we run the script from a normal PowerShell console and do not specify the Exchange Org Name, the script throws an error:
 
 
Obviously, if this is a brand-new Exchange environment, the EMS will not yet be available at this stage. In these cases, we must specify the Exchange Org Name and the script will not use the Get-OrganizationConfig cmdlet:
 
 
From the values presented by the script, and by looking at the Exchange 2016 Active Directory versions table, we can see that this Exchange Environment is running Exchange 2016 CU3.
 
The following is the script that produced the output above:
[CmdletBinding()]
Param (
 [Parameter(Position = 0, Mandatory = $True)]
 [ValidateNotNullOrEmpty()]
 [String] $Domain,

 [Parameter(Position = 1, Mandatory = $False)]
 [String] $ExOrgName = (Get-OrganizationConfig).ID
)


If ($Domain -notmatch "\.") {Write-Host "Please enter a correct Domain name." -ForegroundColor Red; Exit}
ForEach ($name in $Domain.Split(".")) {$tempDomain += ",DC=$($name)"}

$rangeUpper = (DSQUERY.exe * “CN=ms-Exch-Schema-Version-Pt,CN=schema,CN=configuration$($tempDomain)” -Scope base -Attr rangeUpper)[1].Trim()
$objectVersionDNC = (DSQUERY.exe * “CN=Microsoft Exchange System Objects$($tempDomain)” -Scope base -Attr objectVersion)[1].Trim()
$objectVersionCNC = (DSQUERY.exe * “CN=$ExOrgName,CN=Microsoft Exchange,CN=Services,CN=Configuration$($tempDomain)” -Scope base -Attr objectVersion)[1].Trim()
$msExchProductId = (DSQUERY.exe * “CN=$ExOrgName,CN=Microsoft Exchange,CN=Services,CN=Configuration$($tempDomain)” -Scope base -Attr msExchProductId)[1].Trim()

Write-Host "rangeUpper (Schema)           :", $rangeUpper
Write-Host "objectVersion (Default)       :", $objectVersionDNC
Write-Host "objectVersion (Configuration) :", $objectVersionCNC
Write-Host "msExchProductId               :", $msExchProductId


We could take this script one step further and build a list of all these properties and their values for each Exchange CU, and then print which ones match. Because some CUs do not update all these properties, sometimes they will match more than one Exchange version:
 
[CmdletBinding()]
Param (
 [Parameter(Position = 0, Mandatory = $True)]
 [ValidateNotNullOrEmpty()]
 [String] $Domain,

 [Parameter(Position = 1, Mandatory = $False)]
 [String] $ExOrgName = (Get-OrganizationConfig).ID
)


If ($Domain -notmatch "\.") {Write-Host "Please enter a correct Domain name." -ForegroundColor Red; Exit}
ForEach ($name in $Domain.Split(".")) {$tempDomain += ",DC=$($name)"}


$rangeUpperHash = @{}
$rangeUpperHash.Add("Exchange 2016 CU3 and CU4", "15326")
$rangeUpperHash.Add("Exchange 2016 CU2", "15325")
$rangeUpperHash.Add("Exchange 2016 CU1", "15323")
$rangeUpperHash.Add("Exchange 2016 Beta and RTM", "15317")
$rangeUpperHash.Add("Exchange 2013 CU7 and later", "15312")
$rangeUpperHash.Add("Exchange 2013 CU6", "15303")
$rangeUpperHash.Add("Exchange 2013 CU5", "15300")
$rangeUpperHash.Add("Exchange 2013 SP1", "15292")
$rangeUpperHash.Add("Exchange 2013 CU3", "15283")
$rangeUpperHash.Add("Exchange 2013 CU2", "15254")
$rangeUpperHash.Add("Exchange 2013 CU1", "15254")
$rangeUpperHash.Add("Exchange 2013 RTM", "15137")

$objVerDNCHash = @{}
$objVerDNCHash.Add("Exchange 2016 Beta to CU4", "13236")
$objVerDNCHash.Add("Exchange 2013 RTM to CU10", "13236")

$objVerCNCHash = @{}
$objVerCNCHash.Add("Exchange 2016 CU4", "16213")
$objVerCNCHash.Add("Exchange 2016 CU2 and CU3", "16212")
$objVerCNCHash.Add("Exchange 2016 CU1", "16211")
$objVerCNCHash.Add("Exchange 2016 RTM", "16210")
$objVerCNCHash.Add("Exchange 2016 Beta", "16041")
$objVerCNCHash.Add("Exchange 2013 CU10 and later", "16130")
$objVerCNCHash.Add("Exchange 2013 CU6 to CU9", "15965")
$objVerCNCHash.Add("Exchange 2013 CU5", "15870")
$objVerCNCHash.Add("Exchange 2013 SP1", "15844")
$objVerCNCHash.Add("Exchange 2013 CU3", "15763")
$objVerCNCHash.Add("Exchange 2013 CU2", "15688")
$objVerCNCHash.Add("Exchange 2013 CU1", "15614")
$objVerCNCHash.Add("Exchange 2013 RTM", "15449")

$msExchProductIdHash = @{}
$msExchProductIdHash.Add("Exchange 2016 CU5", "15.01.0845.032")
$msExchProductIdHash.Add("Exchange 2016 CU4", "15.01.0669.032")
$msExchProductIdHash.Add("Exchange 2016 CU3", "15.01.0544.027")
$msExchProductIdHash.Add("Exchange 2016 CU2", "15.01.0466.034")
$msExchProductIdHash.Add("Exchange 2016 CU1", "15.01.0396.030")
$msExchProductIdHash.Add("Exchange 2016 RTM", "15.01.0225.042")
$msExchProductIdHash.Add("Exchange 2016 Beta", "15.01.0225.017")
$msExchProductIdHash.Add("Exchange 2013 CU15", "15.00.1263.005")
$msExchProductIdHash.Add("Exchange 2013 CU14", "15.00.1236.003")
$msExchProductIdHash.Add("Exchange 2013 CU13", "15.00.1210.003")
$msExchProductIdHash.Add("Exchange 2013 CU12", "15.00.1178.004")
$msExchProductIdHash.Add("Exchange 2013 CU11", "15.00.1156.006")
$msExchProductIdHash.Add("Exchange 2013 CU10", "15.00.1130.007")
$msExchProductIdHash.Add("Exchange 2013 CU9", "15.00.1104.005")
$msExchProductIdHash.Add("Exchange 2013 CU8", "15.00.1076.009")
$msExchProductIdHash.Add("Exchange 2013 CU7", "15.00.1044.025")
$msExchProductIdHash.Add("Exchange 2013 CU6", "15.00.0995.029")
$msExchProductIdHash.Add("Exchange 2013 CU5", "15.00.0913.022")
$msExchProductIdHash.Add("Exchange 2013 SP1", "15.00.0847.032")
$msExchProductIdHash.Add("Exchange 2013 CU3", "15.00.0775.038")
$msExchProductIdHash.Add("Exchange 2013 CU2", "15.00.0712.024")
$msExchProductIdHash.Add("Exchange 2013 CU1", "15.00.0620.029")
$msExchProductIdHash.Add("Exchange 2013 RTM", "15.00.0516.032")


$rangeUpper = (DSQUERY.exe * "CN=ms-Exch-Schema-Version-Pt,CN=schema,CN=configuration$($tempDomain)" -Scope base -Attr rangeUpper)[1].Trim()
$objVerDNC = (DSQUERY.exe * "CN=Microsoft Exchange System Objects$($tempDomain)" -Scope base -Attr objectVersion)[1].Trim()
$objVerCNC = (DSQUERY.exe * "CN=$ExOrgName,CN=Microsoft Exchange,CN=Services,CN=Configuration$($tempDomain)" -Scope base -Attr objectVersion)[1].Trim()
$msExchProductId = (DSQUERY.exe * "CN=$ExOrgName,CN=Microsoft Exchange,CN=Services,CN=Configuration$($tempDomain)" -Scope base -Attr msExchProductId)[1].Trim()

Write-Host "rangeUpper (Schema)           : $rangeUpper ($(($rangeUpperHash.GetEnumerator() | ? {$_.Value -eq $rangeUpper}).Name -join ' | '))"
Write-Host "objectVersion (Default)       : $objVerDNC ($(($objVerDNCHash.GetEnumerator() | ? {$_.Value -eq $objVerDNC}).Name -join ' | '))"
Write-Host "objectVersion (Configuration) : $objVerCNC ($(($objVerCNCHash.GetEnumerator() | ? {$_.Value -eq $objVerCNC}).Name -join ' | '))"
Write-Host "msExchProductId               : $msExchProductId ($(($msExchProductIdHash.GetEnumerator() | ? {$_.Value -eq $msExchProductId}).Name -join ' | '))"