Microsoft Graph で Microsoft 365 Enterprise 組織内のメールを取得する方法を試してみました。これを実現させるためには、アプリケーションを許可(サインインしたユーザー無しで、バックグランドサービスまたはデーモンとして実行)するように、Azure Active Directory にアプリケーションを登録する必要があります。
NLPやGraph等と組み合わせると、組織の効率化に繋がる分析に使えそうですね。
Azure Active Directory にアプリケーション登録
まずは、組織の Azure Active Directory にアプリケーションを登録します。Microsoft Azureのポータルから、Azure Active Directory に移動してください。
左側のメニューの「アプリの登録」から、「新規登録」を選びます。
登録するアプリの名前を入力し、今回は「シングルテナント」を選択します。入力が終わったら「登録」します。
登録したアプリ(office-app)の画面になるので、「アプリケーション(クライアント)ID」、「ディレクトリ(テナント)ID」をメモしておいてください。
「APIのアクセス許可」から「アクセス許可の追加」をします。
今回は、ディレクトリの読み込みと、ユーザの読み込み、メールの読み込みのアクセスに関して許可を追加します。まずは、「Microsoft Graph」を選択します。
「アプリケーションの許可」を選択して、「User.Read.All」「Directory.Read.All」「Mail.Read.All」のアクセス許可を追加します。検索すると見つけやすいです。(今回のメールとは関係ありませんが、Files.Read.Allも追加しました)
追加した後は、組織のテナントに管理者の同意を与えます。(今回のメールとは関係ありませんが、Files.Read.Allも追加してます)
最後に、アプリからのアクセスに使用するクライアントシークレットを作成します。シークレット(値)は一度しか表示されないので、必ずメモしておいてください。
バックグランドサービスとして動くアプリを作成
まずは、サインインしたユーザー無しで、バックグランドサービスまたはデーモンとして実行できるように、認証プロバイダを作成します。
using Microsoft.Graph;
using Microsoft.Identity.Client;
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace GraphMessagesSample
{
public class ClientSecretAuthProvider : IAuthenticationProvider
{
private IConfidentialClientApplication msalClient;
private string appId;
private string clientSecret;
private string[] scopes;
private string tenantId;
public ClientSecretAuthProvider(string appId, string[] scopes, string tenantId, string clientSecret)
{
this.appId = appId;
this.clientSecret = clientSecret;
this.scopes = scopes;
this.tenantId = tenantId;
this.msalClient = ConfidentialClientApplicationBuilder.Create(this.appId)
.WithAuthority(AzureCloudInstance.AzurePublic, this.tenantId)
.WithClientSecret(this.clientSecret)
.Build();
}
public async Task<string> GetAuthorizationHeader()
{
try
{
var result = await this.msalClient
.AcquireTokenForClient(this.scopes)
.ExecuteAsync();
return result.CreateAuthorizationHeader();
}
catch (Exception exception)
{
Console.WriteLine($"Error getting access token: {exception.Message}");
return null;
}
}
public async Task AuthenticateRequestAsync(HttpRequestMessage requestMessage)
{
requestMessage.Headers.Add("Authorization", await GetAuthorizationHeader());
}
}
}
次に、Graph API から、セキュリティグループ(GetGroupsAsync)、グループのユーザー(GetGroupMembersAsync)、メール(GetMessagesAsync)を取得するプログラムを作成します。
using Microsoft.Graph;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace GraphMessagesSample
{
public class GraphHelper
{
private static GraphServiceClient graphClient;
public static void Initialize(IAuthenticationProvider authProvider)
{
graphClient = new GraphServiceClient(authProvider);
}
public static async Task<IEnumerable<Group>> GetGroupsAsync()
{
try
{
List<Group> groups = new List<Group>();
var resultPage = await graphClient.Groups.Request().GetAsync();
while (true)
{
groups.AddRange(resultPage.CurrentPage);
if (resultPage.NextPageRequest == null)
{
break;
}
resultPage = resultPage.NextPageRequest.GetAsync().Result;
}
return groups;
}
catch (ServiceException ex)
{
Console.WriteLine($"Error getting events: {ex.Message}");
return null;
}
}
public static async Task<IEnumerable<DirectoryObject>> GetGroupMembersAsync(string groupId)
{
try
{
List<DirectoryObject> dirobjects = new List<DirectoryObject>();
var resultPage = await graphClient.Groups[groupId].Members.Request().GetAsync();
while (true)
{
dirobjects.AddRange(resultPage.CurrentPage);
if (resultPage.NextPageRequest == null)
{
break;
}
resultPage = resultPage.NextPageRequest.GetAsync().Result;
}
return dirobjects;
}
catch (ServiceException ex)
{
Console.WriteLine($"Error getting events: {ex.Message}");
return null;
}
}
public static async Task<IEnumerable<Message>> GetMessagesAsync(string userId)
{
try
{
List<Message> dirobjects = new List<Message>();
var resultPage = await graphClient.Users[userId].Messages.Request().GetAsync();
while (true)
{
dirobjects.AddRange(resultPage.CurrentPage);
if (resultPage.NextPageRequest == null)
{
break;
}
resultPage = resultPage.NextPageRequest.GetAsync().Result;
}
return dirobjects;
}
catch (ServiceException ex)
{
Console.WriteLine($"Error getting events: {ex.Message}");
return null;
}
}
}
}
あとは、これらを組み合わせてメールを表示するプログラムを作成します。組織のグループに属しているユーザのメールの件名を全て表示するので、特定グループだけ表示したい場合などはプログラムを変更してください。
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using Microsoft.Graph;
namespace GraphMessagesSample
{
class Program
{
static Dictionary<string, string> LoadClientSecretAppSettings()
{
Dictionary<string, string> result = null;
// Get config ftom AppSettings
var appConfig = new ConfigurationBuilder()
.AddUserSecrets<Program>()
.Build();
var appId = appConfig["appId"];
var scopes = appConfig["scopes"];
var tenantId = appConfig["tenantId"];
var clientSecret = appConfig["clientSecret"];
if (string.IsNullOrEmpty(appId) == false &&
string.IsNullOrEmpty(scopes) == false &&
string.IsNullOrEmpty(tenantId) == false &&
string.IsNullOrEmpty(clientSecret) == false)
{
result = new Dictionary<string, string>()
{
{"appId", appId},
{"scopes", scopes},
{"tenantId", tenantId},
{"clientSecret", clientSecret}
};
}
return result;
}
static void GetMessages()
{
var groups = GraphHelper.GetGroupsAsync().Result;
foreach (var group in groups)
{
Console.WriteLine($"group.Id: {group.Id}");
Console.WriteLine($"group.DisplayName: {group.DisplayName}");
var members = GraphHelper.GetGroupMembersAsync(group.Id).Result;
foreach (Microsoft.Graph.User member in members)
{
Console.WriteLine($"member.Id: {member.Id}");
Console.WriteLine($"member.DisplayName: {member.DisplayName}");
Console.WriteLine($"member.UserPrincipalName: {member.UserPrincipalName}");
var messages = GraphHelper.GetMessagesAsync(member.Id).Result;
foreach (Microsoft.Graph.Message message in messages)
{
Console.WriteLine($"message.Subject: {message.Subject}");
}
}
}
}
static void Main(string[] args)
{
IAuthenticationProvider authProvider = null;
var appConfig = LoadClientSecretAppSettings();
if (appConfig == null)
{
Console.WriteLine("Missing or invalid AppSettings");
return;
}
var appId = appConfig["appId"];
var scopesString = appConfig["scopes"];
var scopes = scopesString.Split(';');
var tenantId = appConfig["tenantId"];
var clientSecret = appConfig["clientSecret"];
// Initialize the auth provider
authProvider = new ClientSecretAuthProvider(appId, scopes, tenantId, clientSecret);
// Initialize Graph client
GraphHelper.Initialize(authProvider);
// Get messages
GetMessages();
}
}
}
実行する
上で書いたコードは、githubにおいてあるので、cloneします。
git clone https://github.com/KentaroAOKI/GraphMessagesSample.git
cd GraphMessagesSample
Azure Active Directory でアプリケーション登録をした際にメモした値を使って設定します。
dotnet user-secrets init
dotnet user-secrets set appId "アプリケーション(クライアント)ID"
dotnet user-secrets set scopes "https://graph.microsoft.com/.default"
dotnet user-secrets set tenantId "ディレクトリ(テナント)ID"
dotnet user-secrets set clientSecret "クライアントシークレット"
ビルドして実行します。
dotnet build
dotnet run
さいごに
メール件名だけでなく、宛先や本文も取得できます。以下のGraph APIドキュメントを参考にしてください。
メールは正しくパースされているし、件名、本文、宛先など、エンコードが統一されて取得できるので、すごく便利ですね。Graph APIの凄さに改めて驚きました。