🎍

Microsoft Entra ID へ ユーザ情報を権限付きで一括登録してみた(C#)

2024/01/03に公開

はじめに

今回は、Azure Active Directory 改め、Microsoft Entra ID を C# で一括登録する方法を見ていきたいと思います。なお、ユーザの一括登録自体は、Azure Portal から作成可能ですが、同時に権限やグループを割り当てる場合は、Azure PowerShell や、Azure CLI によるコマンドレット、もしくは、今回のように C# による実装が必要となります。

本記事の ソースコードは、Git に登録しています。
https://github.com/yutaka-art/AzureEntraUserBulkImporter

サービスプリンシパルを登録する

Azure Portal を介さずに、外部から Entra ID へ接続するには、サービスプリンシパルが必要です。今回は、C# を利用するため、登録していきましょう。

Entra ID をクリックします。

アプリの登録>+新規登録をクリックします。

名前へ任意の文字列を入れ、登録をクリックします。

アプリケーション (クライアント) ID と、ディレクトリ (テナント) ID はプログラムで指定するのでメモっておきましょう。

証明書とシークレットをクリックし、新しいクライアントシークレットをクリックし、追加します。

同様に値の部分をメモっておきます。

APIのアクセス許可 をクリックし、プログラムから利用する権限を追加します。

ユーザ情報Csvファイルを用意する

一括登録するためのCsvファイルを用意します。RoleNameの部分は、ビルトインロールリファレンスを参照してください。

UserCreate.csv 例
DisplayName,MailNickname,UserPrincipalName,Password,RoleName
John Doe,jdoe,jdoe@realisfleuze.onmicrosoft.com,Password123,Global Administrator
Jane Doe,janed,janed@realisfleuze.onmicrosoft.com,Password123,Global Reader
Alex Smith,asmith,asmith@realisfleuze.onmicrosoft.com,Password123,Guest Inviter
Maria Garcia,mgarcia,mgarcia@realisfleuze.onmicrosoft.com,Password123,Identity Governance Administrator
James Johnson,jjohnson,jjohnson@realisfleuze.onmicrosoft.com,Password123,Insights Administrator

ビルトイン ロール リファレンス

# ロール名 ロールID
1 Usage Summary Reports Reader 008a4859-bdcb-459b-b7ec-904fb5e165d6
2 Edge Administrator 02bb4ba5-2ffb-4cf7-9c57-9a86022c7aff
3 Attribute Log Administrator 0398da0f-b648-4635-9415-e218f5c83c67
4 Compliance Data Administrator 042cec86-f0d6-46de-93d5-75fafe38bf52
5 Identity Governance Administrator 05d80b1e-52a6-4b86-a310-a43d937bea16
6 Authentication Policy Administrator 0af2454c-b99f-4045-9f3d-1288a970fe6d
7 Permissions Management Administrator 0bdf5e13-0439-4aa2-8208-cb49c3800a5b
8 Insights Analyst 10ef4940-d22c-4183-941f-1cd07d2132e8
9 Cloud Application Administrator 122a99c3-c475-453b-867b-01c14f8501e9
10 Virtual Visits Administrator 128fc433-85c2-4eba-97a9-66390a21ae71
11 Authentication Administrator 16a29b8f-7507-4113-8852-786ac36843d5
12 Exchange Recipient Administrator 174baec1-eb67-4ef1-8401-1e812268d1da
13 Security Administrator 197253ba-1252-4ea6-9872-66f4d640bec8
14 Attack Payload Author 21cafa97-86c7-44a9-ad30-eb7af65563a7
15 Hybrid Identity Administrator 21f072d5-eb54-452e-93de-3dcfebca1748
16 Exchange Administrator 2a2b54bf-a200-404c-8692-68691c401662
17 Reports Reader 2a69cf00-5226-4156-82e0-0f9e15642114
18 Search Administrator 3662bd4e-73cf-41e3-a8ac-72917f2fefe6
19 SharePoint Administrator 38de9f5f-e9bf-4bfd-8a55-6e9aca01b318
20 Microsoft Hardware Warranty Administrator 3e118278-7d7c-4392-8d45-555f645ff409
21 Dynamics 365 Administrator 43da951b-73e2-4db8-a52d-43ca76060a8f
22 External Identity Provider Administrator 4455dfbe-e3b4-416b-9e72-f3523cfa99bb
23 Intune Administrator 46ba74ec-0417-476d-a606-0e5a8abe553c
24 Attribute Definition Administrator 48f7933f-f586-4ac3-bdfd-10335ece3730
25 Directory Readers 4b37053c-1a83-42f7-83b1-c9669ec0dd5e
26 Security Reader 4c5a5dda-02ee-4ec2-b28a-a8a68fa0bf44
27 Domain Name Administrator 4d953343-72fa-4c79-af57-3d70680076fc
28 Printer Technician 4e61eb18-aff9-4317-8393-e516496bec6e
29 Global Administrator 50e9a13a-d631-4ffe-96e1-213a2d062292
30 Attribute Definition Reader 50f9bc21-538f-45c5-88aa-6427cbf6ee8a
31 Privileged Role Administrator 5525e09a-1394-4631-ad13-4d42f5c13654
32 Azure Information Protection Administrator 5653b2fd-5eec-415b-b0a4-ff95ebc59ab8
33 Message Center Reader 5c3a6d0d-cfb3-4272-9e77-b6b91907f3a6
34 B2C IEF Keyset Administrator 5cb8bd27-68e4-47ce-b827-4c2b2f1f7b22
35 Extended Directory User Administrator 5e1d4964-b92d-482e-82af-67acbad6be01
36 Compliance Administrator 63992f23-9104-46d6-854a-c1de8d20e92f
37 Cloud Device Administrator 64515973-240e-4199-8e0c-987dc63c6c67
38 Teams Communications Support Specialist 6495e6a6-2715-4a0a-98cb-23aff40604c1
39 Directory Writers 695df5e6-bdb8-4eac-b800-43d4f591fba5
40 B2C IEF Policy Administrator 6ac6a26b-ee0d-4a77-a11e-0bb37a1ef392
41 Azure AD Joined Device Local Administrator 6b7aa8f0-7b33-4c73-9cb8-5c2524eb937f
42 Network Administrator 709597d6-299d-4890-9c26-c4cb3a2ca841
43 Viva Goals Administrator 71c5bc03-546c-4614-b98e-b4fbbf080644
44 Cloud App Security Administrator 72daa88e-f9ac-4157-b9c1-640716aa8c20
45 External ID User Flow Attribute Administrator 7e69e700-9dc3-40fb-93c9-dd008f0cf904
46 Tenant Creator 800f4fbe-eaad-4fbd-8354-d06e8ab3ce56
47 Groups Administrator 81b59aa8-da44-4b18-9732-7347e1e4c175
48 Printer Administrator 823fbb40-af27-402c-a1d8-88a4f5d9ff34
49 Azure DevOps Administrator 83c910c3-3530-4c9c-9365-e8c445ab2b69
50 Organizational Messages Writer 845ee3fd-6225-4f59-99c9-266ac76544e6
51 User Administrator 891f52da-adb4-4eba-b645-ed37bbac1f11
52 Microsoft Hardware Warranty Specialist 8ae3e709-b9dc-4029-ac48-a8ec75f5ae8d
53 Privileged Authentication Administrator 8d5a4d54-79c9-44dc-b29a-fea249bb39f2
54 Viva Pulse Administrator 8fcd9373-e612-4310-9b60-fe36a33a3704
55 Teams Administrator 9796c49d-19a3-42f9-8729-886a4743ae97
56 Global Reader 996dd2d1-83c6-40cf-9b63-9d2ee0c82581
57 Attribute Assignment Administrator 9adaef55-6632-41a6-a38e-69014027f197
58 Message Center Privacy Reader 9af871f6-26fc-4075-abe7-e5d40a6bc48a
59 Desktop Analytics Administrator 9f54c392-c4f7-43c2-8180-9762bcce325b
60 Security Operator 9f60703f-7d68-4b1b-a0c5-fd1ee5692efd
61 Search Editor a13d0704-c97d-42f3-b5e9-e5a721490cdc
62 Lifecycle Workflows Administrator a28d0ff9-a6b8-4828-b2f9-6aa259161f27
63 Application Developer a3c3c495-846d-440e-b8c9-ac9f1f0a4b4a
64 Windows Update Deployment Administrator a651122d-1417-4a2a-9723-b4d452034cb1
65 Teams Communications Support Engineer a6b333d1-de2a-42cb-8bc6-f7ec5df9f11f
66 Conditional Access Administrator ab434954-92be-4e1e-b2ba-5689b606ec95
67 Windows 365 Administrator b00f3766-9fa3-4b13-9e2c-ea23fe4f4ed2
68 Attack Simulation Administrator b7aa3fc0-ee2e-4443-9b97-a6f9d05de2fb
69 License Administrator b8790d00-4dfc-44b0-9d7a-7b2eb17816ec
70 Authentication Extensibility Administrator b89a3e0c-8ea3-403f-91c6-3083c465e49e
71 Insights Business Leader bb7dc0d3-7489-4ba8-a3a0-797490add6f9
72 Power Platform Administrator c4cb3c7d-a98b-4110-8c05-12ec8eeab861
73 Insights Administrator ca478cb2-c67b-40b3-974e-45664633a182
74 Kaizala Administrator cb39e494-1e93-472c-aec7-64adafaf16fe
75 Service Support Administrator ce0d0715-d3ff-4df2-abe0-6ac96471ec58
76 Teams Devices Administrator ce515022-997c-41aa-a8d2-9bd49abfdcee
77 External ID User Flow Administrator d08a35d4-c377-4141-9c5f-2403424f8d5f
78 Attribute Assignment Reader d3e825d7-dadb-4829-8298-09bf382859f8
79 Helpdesk Administrator d8506869-0c9e-4e6c-971e-484c28bc3d7d
80 User Experience Success Manager dc0922fb-f236-41dc-a71f-8d62d3e87c52
81 Knowledge Manager dc7a6a35-8503-406f-a991-9b79fd08e779
82 Knowledge Administrator dccf39d7-ce69-49ed-b74d-f2994f4f594b
83 Password Administrator dce8b44e-e0b2-42fd-b332-1fa2ae3cf34f
84 Skype for Business Administrator e3ecf083-3fce-46c8-9354-6bf54fb5e1af
85 Fabric Administrator e3fd3acc-9d7b-415a-8239-d277eb0bf08d
86 Office Apps Administrator e94161c7-46b9-463c-b6f8-4d961b6efec1
87 Yammer Administrator eb67b367-b746-426d-b2c4-095b444f94b0
88 Teams Communications Administrator ec832fda-7852-456b-b0ff-7548521f5724
89 Customer LockBox Access Approver ee00f06f-f880-406b-befc-333b64de0625
90 Guest Inviter f295f3e0-f01c-4402-b75e-4dca68f05aac
91 Global Secure Access Administrator f36d911c-a98a-454f-9c58-322ce47e19ba
92 Billing Administrator f500c28f-3604-4273-8365-c06d7ee4bb9f
93 Application Administrator fa822aea-7d61-42ca-8075-7264f39731d5
94 Attribute Log Reader feaa6a48-e4e0-466e-8bed-3309f4ba32ee

プログラムの実装

コンソールアプリケーションとして、ソリューション構成は下記で実装します。

Azure テナント情報は、appsettings.jsonに記載します。このファイルを環境に合わせて更新してください。

appsettings.json
{
  "AzureAd": {
    "ClientId": "insert ClientId here",
    "TenantId": "insert TenantId here",
    "ClientSecret": "insert ClientSecret here"
  }
}

GraphServiceClientCsv登録するサービスクラスを DI します。

DI/DIContainer.cs
using AzureEntraUserBulkImporter.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Graph;
using Microsoft.Identity.Client;

namespace AzureEntraUserBulkImporter.DI
{
    #region [DIContainer]
    /// <summary>
    /// 依存性注入コンテナを提供するクラス。
    /// アプリケーションのサービスと構成を管理します。
    /// </summary>
    public static class DIContainer
    {
        // サービスプロバイダーのインスタンス
        private static ServiceProvider Provider;

        // 構成設定のインスタンス
        private static IConfiguration Configuration;

        /// <summary>
        /// 静的コンストラクタで構成とサービスを初期化します。
        /// </summary>
        static DIContainer()
        {
            InitConfiguration();
            Provider = ConfigureServices();
        }

        /// <summary>
        /// アプリケーションの構成を初期化します。
        /// appsettings.json と環境変数から設定を読み込みます。
        /// </summary>
        private static void InitConfiguration()
        {
            var builder = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        /// <summary>
        /// アプリケーションで利用するサービスを設定します。
        /// ロギング、EntraID認証、カスタム認証プロバイダ、メインプロセスサービスを登録します。
        /// </summary>
        /// <returns>構成されたサービスプロバイダー</returns>
        private static ServiceProvider ConfigureServices()
        {
            var services = new ServiceCollection();

            services.AddLogging(builder =>
            {
                builder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace)
                       .AddConfiguration(Configuration.GetSection("Logging"))
                       .AddConsole()
                       .AddDebug();
            });

            services.AddSingleton<IConfiguration>(Configuration);
            services.Configure<EntraIdConfig>(Configuration.GetSection("AzureAd"));

            // IConfidentialClientApplication の DI 登録
            services.AddSingleton<IConfidentialClientApplication>(provider =>
            {
                var EntraIdConfig = provider.GetRequiredService<IOptions<EntraIdConfig>>().Value;
                return ConfidentialClientApplicationBuilder
                    .Create(EntraIdConfig.ClientId)
                    .WithTenantId(EntraIdConfig.TenantId)
                    .WithClientSecret(EntraIdConfig.ClientSecret)
                    .Build();
            });

            // CustomAuthProvider の DI 登録
            services.AddSingleton<CustomAuthProvider>();

            // GraphServiceClient の DI 登録
            services.AddSingleton<GraphServiceClient>(provider =>
            {
                var authProvider = provider.GetRequiredService<CustomAuthProvider>();
                return new GraphServiceClient(authProvider);
            });

            // IMainProcessService の DI 登録
            services.AddTransient<IMainProcessService, MainProcessService>();

            return services.BuildServiceProvider();
        }

        /// <summary>
        /// 指定された型のサービスインスタンスを取得します。
        /// </summary>
        /// <typeparam name="TService">取得するサービスの型</typeparam>
        /// <returns>サービスのインスタンス</returns>
        public static TService GetService<TService>()
        {
            return Provider.GetService<TService>();
        }
    }
    #endregion
}

GraphServiceClient で利用するアクセストークンを取得するために、カスタム認証プロバイダーを実装します。

CustomAuthProvider.cs
using Microsoft.Graph;
using Microsoft.Identity.Client;
using System.Net.Http;
using System.Threading.Tasks;

namespace AzureEntraUserBulkImporter.Services
{
    /// <summary>
    /// カスタム認証プロバイダー。
    /// Microsoft Graph API へのリクエストにアクセストークンを提供します。
    /// </summary>
    public class CustomAuthProvider : IAuthenticationProvider
    {
        private IConfidentialClientApplication ClientApp;

        /// <summary>
        /// CustomAuthProvider の新しいインスタンスを初期化します。
        /// </summary>
        /// <param name="clientApp">認証に使用する IConfidentialClientApplication。</param>
        public CustomAuthProvider(IConfidentialClientApplication clientApp)
        {
            this.ClientApp = clientApp;
        }

        /// <summary>
        /// HTTPリクエストに認証情報を付与します。
        /// </summary>
        /// <param name="request">認証が必要な HTTPリクエスト。</param>
        /// <returns>非同期操作</returns>
        public async Task AuthenticateRequestAsync(HttpRequestMessage request)
        {
            // アプリケーションのアクセストークンを取得
            var authResult = await this.ClientApp.AcquireTokenForClient(new[] { "https://graph.microsoft.com/.default" }).ExecuteAsync();

            // HTTPリクエストに認証ヘッダーを追加
            request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authResult.AccessToken);
        }
    }
}

Csvより情報を取得し、GraphServiceClient 経由で、Microsoft Entra ID へ登録します。

Services/MainProcessService.cs
using AzureEntraUserBulkImporter.Models;
using Microsoft.Extensions.Options;
using Microsoft.Graph;
using Microsoft.Identity.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AzureEntraUserBulkImporter.Services
{
    #region IMainProcessService
    /// <summary>
    /// Azure EntraID へユーザー情報を一括登録するプロセスインタフェースクラス
    /// </summary>
    public interface IMainProcessService : IDisposable
    {
        /// <summary>
        /// メインプロセスの実行を行います。
        /// </summary>
        /// <param name="cts">キャンセレーショントークンソース。プロセスのキャンセルに使用されます。</param>
        /// <returns>非同期操作のタスク</returns>
        public Task MainProcess(CancellationTokenSource cts);
    }
    #endregion

    #region MainProcessService
    /// <summary>
    /// Azure EntraID へユーザー情報を一括登録するプロセス実装クラス
    /// </summary>
    public class MainProcessService : IMainProcessService
    {
        #region Variable・Const
        private readonly EntraIdConfig EntraIdConfig;
        private readonly IConfidentialClientApplication ConfidentialClientApplication;
        private readonly GraphServiceClient GraphClient;
        #endregion

        #region Constructor
        /// <summary>
        /// MainProcessService の新しいインスタンスを初期化します。
        /// </summary>
        /// <param name="EntraIdConfig">EntraID の設定。</param>
        /// <param name="confidentialClientApplication">EntraID との通信に使用するクライアントアプリケーション。</param>
        /// <param name="graphClient">Microsoft Graph API へのリクエストを処理するクライアント。</param>
        public MainProcessService(
            IOptions<EntraIdConfig> EntraIdConfig,
            IConfidentialClientApplication confidentialClientApplication,
            GraphServiceClient graphClient)
        {
            this.EntraIdConfig = EntraIdConfig.Value;
            this.ConfidentialClientApplication = confidentialClientApplication;
            this.GraphClient = graphClient;
        }
        #endregion

        #region Method
        /// <summary>
        /// Azure EntraID へユーザー情報を一括登録するプロセスを実行します。
        /// </summary>
        /// <param name="cts">キャンセレーショントークンソース。</param>
        public async Task MainProcess(CancellationTokenSource cts)
        {
            Console.WriteLine("Press any key to start user registration.");
            Console.ReadKey();

            // ユーザー情報をCsvより取得
            var userRegisterInfos = GetUsersToRegister();

            foreach (var userRegisterInfo in userRegisterInfos)
            {
                try
                {
                    // ユーザ属性情報
                    var user = new User
                    {
                        DisplayName = userRegisterInfo.DisplayName,
                        MailNickname = userRegisterInfo.MailNickname,
                        UserPrincipalName = userRegisterInfo.UserPrincipalName,
                        PasswordProfile = new PasswordProfile
                        {
                            ForceChangePasswordNextSignIn = true,
                            Password = userRegisterInfo.Password
                        },
                        AccountEnabled = true
                    };

                    // 全ユーザを取得し、LINQで対象のユーザが存在するか確認
                    var users = await this.GraphClient.Users.Request().GetAsync();
                    var isExist = users.CurrentPage.Any(x => x.UserPrincipalName == user.UserPrincipalName);
                    if (isExist == true)
                    {
                        Console.WriteLine($"User: {user.UserPrincipalName} is already exist.");
                        continue;
                    }

                    // ユーザーを登録
                    var afterInfo = await this.GraphClient.Users.Request().AddAsync(user);

                    // 対象のロールを取得
                    var targetRole = await this.GraphClient.DirectoryRoles
                        .Request()
                        .Filter($"displayName eq '{userRegisterInfo.RoleName}'")
                        .GetAsync();

                    var targetRoleId = targetRole.CurrentPage.FirstOrDefault()?.Id;

                    // 対象のロールが見つかった場合、ユーザーをロールに追加
                    if (targetRoleId != null)
                    {
                        var directoryRoleAssignment = new DirectoryObject
                        {
                            Id = afterInfo.Id
                        };

                        await this.GraphClient.DirectoryRoles[targetRoleId].Members.References
                            .Request()
                            .AddAsync(directoryRoleAssignment);
                    }

                    Console.WriteLine($"User: {afterInfo.DisplayName}, {afterInfo.Mail} is registered.");
                }
                catch (ServiceException ex)
                {
                    Console.WriteLine($"Error registering user or assigning role: {ex.Message}");
                }
            }
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }

        /// <summary>
        /// Csvファイルからユーザー情報を取得します。
        /// </summary>
        /// <returns></returns>
        private IEnumerable<UserData> GetUsersToRegister()
        {
            var csvFilePath = "UserCreate.csv"; // CSVファイルのパスを設定

            return System.IO.File.ReadAllLines(csvFilePath)
                .Skip(1) // ヘッダー行をスキップ
                .Select(line => line.Split(','))
                .Select(parts => new UserData
                {
                    DisplayName = parts[0],
                    MailNickname = parts[1],
                    UserPrincipalName = parts[2],
                    Password = parts[3],
                    RoleName = parts[4]
                });
        }
        #endregion

        #region Dispose
        private bool _isDisposed = false;

        /// <summary>
        /// 使用済みのリソースを解放します。
        /// </summary>
        public void Dispose()
        {
            if (this._isDisposed) return;

            this._isDisposed = true;
        }
        #endregion
    }
    #endregion
}

実行すると下記のように登録結果が表示されます。

ユーザの追加が確認できます。

ロールも正常に付与されているようです。

まとめ

この記事では、C# を使用して Microsoft Entra ID へユーザ情報を一括で登録し、適切なロールを割り当てるプロセスを説明しました。Azure Portal を使わずにプログラムから直接操作するためには、サービスプリンシパルの設定、適切なAPIアクセス権限の付与が必要となります。

References

https://www.microsoft.com/ja-jp/security/business/identity-access/microsoft-entra-id
https://learn.microsoft.com/ja-jp/entra/identity/role-based-access-control/permissions-reference

GitHubで編集を提案

Discussion