Exchange の SCP 参照を使用して自動検出エンドポイントを見つける
Active Directory ドメイン サービス (AD DS) で自動検出 SCP オブジェクトを見つけ、それらのオブジェクトを使用して、Exchange 自動検出サービスで使用する自動検出エンドポイント URL を検出する方法について説明します。
自動検出を使用すると、Exchange サーバー上のメールボックスに接続するために必要な情報を簡単に取得できます。 ただし、自動検出を使用するには、設定の取得対象であるユーザーに適した自動検出サーバーを見つける手段が必要になります。 AD DS のサービス接続ポイント (SCP) オブジェクトによって、ドメインに参加しているクライアントは自動検出サーバーを簡単に検索することができます。
自動検出エンドポイントを検出できるようにセットアップする
AD DS で自動検出 SCP オブジェクトを見つけるには、以下のものに対するアクセス権が必要です。
Exchange 2007 SP1 以降の Exchange オンプレミス バージョンを実行しているサーバー。
Exchange サーバーがインストールされているドメインに参加しているクライアント コンピューター。
Exchange サーバーにメールボックスがあるユーザー アカウント。
また、作業を開始する前に、いくつかの基本的概念に精通しておきます。 以下に、役立ついくつかのリソースを記します。
表 1. SCP オブジェクトから自動検出エンドポイントを検索するための関連記事
関連記事 | 内容 |
---|---|
Exchange の自動検出 |
自動検出サービスの動作方法。 |
サービス接続ポイントを使用して発行する |
SCP オブジェクトを使用してサービス固有のデータを発行する方法。 |
AD DS で自動検出 SCP オブジェクトを見つける
AD DS に発行されている自動検出エンドポイントを検索するための第一歩は、自動検出 SCP オブジェクトを見つけることです。 Exchange は、次の 2 種類の自動検出の SCP オブジェクトを発行します。
SCP ポインター — このポインターには、対象ユーザーのドメインの自動検出 SCP オブジェクトを見つけるために使用する必要がある特定の LDAP サーバーをポイントする情報が含まれます。 SCP ポインターには、67661d7F-8FC4-4fa7-BFAC-E1D7794C1F68 という GUID が割り当てられています。
SCP URL — 自動検出エンドポイントの URL が含まれます。 SCP URL には、77378F46-2C66-4aa9-A6A6-3E7A48B19596 という GUID が割り当てられています。
自動検出 SCP オブジェクトを見つけるには
AD DS のルート DSE エントリの configurationNamingContext プロパティを読み取り、ドメインの構成名前付けコンテキストへのパスを取得します。 そのためには、DirectoryEntry クラス、または AD DS にアクセスできる他の任意の API を使用できます。
keywords プロパティに SCP ポインター GUID か SCP URL GUID のどちらかが含まれる構成名前付けコンテキストで、SCP オブジェクトを検索します。
検出した SCP オブジェクトが、対象ユーザーのドメインをスコープとする SCP ポインターかチェックします。そのためには、エントリの keywords プロパティが
"Domain=<domain>"
に等しいかどうかを確認します。 たとえば、ユーザーの電子メール アドレスが の場合は elvin@contoso.com、 と等しい キーワード プロパティ内のエントリを持つ SCP ポインターを"Domain=contoso.com"
探します。 一致する SCP ポインターが見つかる場合、SCP オブジェクト セットを破棄し、serviceBindingInformation プロパティの値を、ルート DSE エントリが接続するサーバーとして使用して手順 1 からやり直します。ユーザーのドメインをスコープとする SCP ポインターが見つからない場合、ドメインをスコープとしない SCP ポインターをチェックし、現在のサーバーが結果をまったく返さない場合に備えて、serviceBindingInformation プロパティの値を「フォールバック」サーバーとして保存します。
対象ドメインをスコープとする SCP ポインターがまったく見つからなくなれば、次の手順に進めます。次の手順では、結果から自動検出エンドポイントの優先順位を付けた一覧を生成します。
自動検出エンドポイントの優先順位を付けた一覧を生成する
見つけた一連の SCP オブジェクトを使用して、優先順位を付けた自動検出エンドポイント URL の一覧を生成できます。そのためには次の手順を実行します。
クライアント コンピューターの Active Directory サイト名を取得します。
見つけた一連の SCP オブジェクトの各 SCP URL の keywords プロパティをチェックし、次の規則に基づいて URL に優先順位を割り当てます。
keywords プロパティに
"Site=<site name>"
(<site name>
は前述の手順で取得した Active Directory サイト名に等しい) という値が含まれている場合、URL に優先順位 1 を割り当てます。keywords プロパティに
"Site="
で始まる値のエントリが含まれていない場合、URL に優先順位 2 を割り当てます。keywords プロパティに
"Site=<site name>
(<site name>
は前述の手順で取得した Active Directory サイト名と等しくない) という値が含まれている場合、URL に優先順位 3 を割り当てます。
コード例: SCP 参照の実行
次のコード例は、自動検出 SCP オブジェクトを見つけ、自動検出エンドポイントの優先順位が付けられた一覧を生成する方法を示しています。
using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;
namespace ScpLookup
{
// This sample is for demonstration purposes only. Before you run this sample, make sure
// that the code meets the coding requirements of your organization.
class Program
{
static void Main(string[] args)
{
string domain = args[0];
Console.WriteLine("Performing SCP lookup for {0}.", domain);
List<string> scpUrls = GetScpUrls(null, domain);
Console.WriteLine("\nOrdered List of Autodiscover endpoints:");
foreach (string url in scpUrls)
{
Console.WriteLine(" {0}", url);
}
Console.WriteLine("SCP lookup done.");
}
// GUID for SCP URL keyword.
private const string ScpUrlGuidString = @"77378F46-2C66-4aa9-A6A6-3E7A48B19596";
// GUID for SCP pointer keyword.
private const string ScpPtrGuidString = @"67661d7F-8FC4-4fa7-BFAC-E1D7794C1F68";
static List<string> GetScpUrls(string ldapServer, string domain)
{
// Create a new list to return.
List<string> scpUrlList = new List<string>();
string rootDSEPath = null;
// If ldapServer is null/empty, use LDAP://RootDSE to
// connect to Active Directory Domain Services (AD DS). Otherwise, use
// LDAP://SERVERNAME/RootDSE to connect to a specific server.
if (string.IsNullOrEmpty(ldapServer))
{
rootDSEPath = "LDAP://RootDSE";
}
else
{
rootDSEPath = ldapServer + "/RootDSE";
}
SearchResultCollection scpEntries = null;
try
{
// Get the root directory entry.
DirectoryEntry rootDSE = new DirectoryEntry(rootDSEPath);
// Get the configuration path.
string configPath = rootDSE.Properties["configurationNamingContext"].Value as string;
// Get the configuration entry.
DirectoryEntry configEntry = new DirectoryEntry("LDAP://" + configPath);
// Create a search object for the configuration entry.
DirectorySearcher configSearch = new DirectorySearcher(configEntry);
// Set the search filter to find SCP URLs and SCP pointers.
configSearch.Filter = "(&(objectClass=serviceConnectionPoint)" +
"(|(keywords=" + ScpPtrGuidString + ")(keywords=" + ScpUrlGuidString + ")))";
// Specify which properties you want to retrieve.
configSearch.PropertiesToLoad.Add("keywords");
configSearch.PropertiesToLoad.Add("serviceBindingInformation");
scpEntries = configSearch.FindAll();
}
catch (Exception ex)
{
Console.WriteLine("SCP lookup failed with: ");
Console.WriteLine(ex.ToString());
}
// If no SCP entries were found, then exit.
if (scpEntries == null || scpEntries.Count <= 0)
{
Console.WriteLine("No SCP records found.");
return null;
}
string fallBackLdapPath = null;
// Check for SCP pointers.
foreach (SearchResult scpEntry in scpEntries)
{
ResultPropertyValueCollection entryKeywords = scpEntry.Properties["keywords"];
if (CollectionContainsExactValue(entryKeywords, ScpPtrGuidString))
{
string ptrLdapPath = scpEntry.Properties["serviceBindingInformation"][0] as string;
// Determine whether this pointer is scoped to the user's domain.
if (CollectionContainsExactValue(entryKeywords, "Domain=" + domain))
{
Console.WriteLine("Found SCP pointer for " + domain + " in " + scpEntry.Path);
// Restart SCP lookup with the server assigned for the domain.
Console.WriteLine("Restarting SCP lookup in " + ptrLdapPath);
return GetScpUrls(ptrLdapPath, domain);
}
else
{
// Save the first SCP pointer that is not scoped to a domain as a fallback
// in case you do not get any results from this server.
if (entryKeywords.Count == 1 && string.IsNullOrEmpty(fallBackLdapPath))
{
fallBackLdapPath = ptrLdapPath;
Console.WriteLine("Saved fallback SCP pointer: " + fallBackLdapPath);
}
}
}
}
string computerSiteName = null;
try
{
// Get the name of the ActiveDirectorySite the computer
// belongs to (if it belongs to one).
ActiveDirectorySite site = ActiveDirectorySite.GetComputerSite();
computerSiteName = site.Name;
Console.WriteLine("Local computer in site: " + computerSiteName);
}
catch (Exception ex)
{
Console.WriteLine("Unable to get computer site name.");
Console.WriteLine(ex.ToString());
}
if (!string.IsNullOrEmpty(computerSiteName))
{
// Scan the search results for SCP URLs.
// SCP URLs fit into three tiers:
// Priority 1: The URL is scoped to the computer's Active Directory site.
// Priority 2: The URL is not scoped to any Active Directory site.
// Priority 3: The URL is scoped to a different Active Directory site.
// Temporary lists to hold priority 2 and 3 URLs.
List<string> priorityTwoUrls = new List<string>();
List<string> priorityThreeUrls = new List<string>();
foreach (SearchResult scpEntry in scpEntries)
{
ResultPropertyValueCollection entryKeywords = scpEntry.Properties["keywords"];
// Check for SCP URLs.
if (CollectionContainsExactValue(entryKeywords, ScpUrlGuidString))
{
string scpUrlPath = scpEntry.Properties["adsPath"][0] as string;
Console.WriteLine("SCP URL found at {0}", scpUrlPath);
string scpUrl = scpEntry.Properties["serviceBindingInformation"][0] as string;
scpUrl = scpUrl.ToLower();
// Determine whether this entry is scoped to the computer's site.
if (CollectionContainsExactValue(entryKeywords, "Site=" + computerSiteName))
{
// Priority 1.
if (!scpUrlList.Contains(scpUrl.ToLower()))
{
Console.WriteLine("Adding priority 1 SCP URL: {0}", scpUrl.ToLower());
scpUrlList.Add(scpUrl);
}
else
{
Console.WriteLine("Priority 1 SCP URL already found: {0}", scpUrl);
}
}
else
{
// Determine whether this is a priority 2 or 3 URL.
if (CollectionContainsPrefixValue(entryKeywords, "Site="))
{
// Priority 3.
if (!priorityThreeUrls.Contains(scpUrl))
{
Console.WriteLine("Adding priority 3 SCP URL: {0}", scpUrl);
priorityThreeUrls.Add(scpUrl);
}
else
{
Console.WriteLine("Priority 3 SCP URL already found: {0}", scpUrl);
}
}
else
{
// Priority 2.
if (!priorityTwoUrls.Contains(scpUrl))
{
Console.WriteLine("Adding priority 2 SCP URL: {0}", scpUrl);
priorityTwoUrls.Add(scpUrl);
}
else
{
Console.WriteLine("Priority 2 SCP URL already found: {0}", scpUrl);
}
}
}
}
}
// Now add the priority 2 URLs into the main list.
foreach (string priorityTwoUrl in priorityTwoUrls)
{
// If the URL is already in the list as a priority 1,
// don't add it again.
if (!scpUrlList.Contains(priorityTwoUrl))
{
scpUrlList.Add(priorityTwoUrl);
}
}
// Now add the priority 3 URLs into the main list.
foreach (string priorityThreeUrl in priorityThreeUrls)
{
// If the URL is already in the list as a priority 1
// or priority 2, don't add it again.
if (!scpUrlList.Contains(priorityThreeUrl))
{
scpUrlList.Add(priorityThreeUrl);
}
}
// If after all this, you still have no URLs in your list,
// try the fallback SCP pointer, if you have one.
if (scpUrlList.Count == 0 && fallBackLdapPath != null)
{
return GetScpUrls(fallBackLdapPath, domain);
}
}
return scpUrlList;
}
private static bool CollectionContainsExactValue(ResultPropertyValueCollection collection, string value)
{
foreach (object obj in collection)
{
string entry = obj as string;
if (entry != null)
{
if (string.Compare(value, entry, true) == 0)
return true;
}
}
return false;
}
private static bool CollectionContainsPrefixValue(ResultPropertyValueCollection collection, string value)
{
foreach (object obj in collection)
{
string entry = obj as string;
if (entry != null)
{
if (entry.StartsWith(value))
return true;
}
}
return false;
}
}
}
次の手順
自動検出プロセスの次の手順は、検出した URL に自動検出要求を送信し、優先順位 1 の URL、次に優先順位 2 の URL、最後に優先順位 3 の URL から始めます。 自動検出要求を送信し、応答を処理する方法の詳細については、次の記事を参照してください。