Alternative Architecture DOJO

オルターブースのクラウドネイティブ特化型ブログです。

GitHub Copilotを活用してGraph APIを使ったスクリプトを作成しました

こんにちは!今年2月にオルターブースに入社した、實成と申します。

今回は、GitHub Copilotを活用して、Microsoft 365のユーザー新規登録のスクリプトを作成しましたので、その内容についてお話しします。

目次

注意事項

このブログでは、生成AIを活用して作成されたコンテンツを含んでいます。以下の点にご留意ください。

正確性の保証:

生成AIから生成された情報は、必ずしも正確であるとは限りません。最新の情報や専門的なアドバイスが必要な場合は、信頼できるソースを確認してください。

内容の検証:

生成されたコンテンツは参考情報として提供されており、最終的な判断や決定を行う際には、必ずご自身で内容を検証してください。

背景と目的

この仕事を任されたとき、私は入社して間もなく、最低限のプログラミングの学習はしていたものの、IT業界は未経験でした。Microsoftのサービスについても勉強し始めたばかりで、Microsoft Graph APIに関する知識もありませんでした。

今回の目的は、ユーザー情報が記載されているCSVファイルを参照して、Microsoft365ユーザーとして自動登録するスクリプトを作成することです。

このスクリプトのユーザー登録の際に、Graph APIというAPIを利用します。

「サービスの知識がない自分に作ることができるだろうか」と不安に感じていましたが、GitHub Copilotを活用して開発を進めることで、目的のスクリプトを作成することができました。

今回の記事が参考になれば幸いです。

Microsoft Graph APIの概要

Microsoft 365やMicrosoft Entraなどのクラウドサービスにアクセスするためのツールです。Microsoft Graph APIでは、APIを使用することで、ユーザーのプロファイル情報などのデータにアクセスし、アプリを開発することができます。

learn.microsoft.com

スクリプトの詳細

  • GraphAPIを使用して、ユーザー自動登録のスクリプトを作成する。
  • ユーザー情報は、csvファイルを参照する。
  • csvファイルには、ユーザーの名前、メールアドレス、役職の情報が含まれる。
  • 使用言語はJavaScriptである。

下の画像は、整理した情報を基に、最初に入力したプロンプトです。

回答結果

まずは、実装の概要と必要なパッケージ。

メインとなるユーザー自動登録スクリプト

const fs = require('fs');
const csv = require('csv-parser');
const { ClientSecretCredential } = require('@azure/identity');
const { Client } = require('@microsoft/microsoft-graph-client');
const { TokenCredentialAuthenticationProvider } = require('@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials');

// Microsoft Entra ID アプリケーション登録の情報
const tenantId = 'あなたのテナントID';
const clientId = 'あなたのクライアントID';
const clientSecret = 'あなたのクライアントシークレット';
const csvFilePath = './users.csv';

// Microsoft Graph クライアントの初期化
function initializeGraphClient() {
  const credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
  const authProvider = new TokenCredentialAuthenticationProvider(credential, {
    scopes: ['https://graph.microsoft.com/.default']
  });

  return Client.initWithMiddleware({
    authProvider: authProvider
  });
}

// ユーザーを作成する関数
async function createUser(graphClient, userData) {
  try {
    const user = {
      accountEnabled: true,
      displayName: userData.displayName,
      mailNickname: userData.mailNickname || userData.displayName.replace(/\s/g, '').toLowerCase(),
      userPrincipalName: userData.userPrincipalName,
      passwordProfile: {
        forceChangePasswordNextSignIn: true,
        password: generateRandomPassword()
      },
      jobTitle: userData.jobTitle || '',
      department: userData.department || '',
      usageLocation: userData.usageLocation || 'JP'
    };

    console.log(`ユーザー作成中: ${userData.displayName}`);
    const result = await graphClient.api('/users').post(user);
    console.log(`ユーザー作成成功: ${result.displayName} (ID: ${result.id})`);
    return result;
  } catch (error) {
    console.error(`ユーザー作成エラー: ${userData.displayName}`, error);
    throw error;
  }
}

// ランダムなパスワード生成関数
function generateRandomPassword() {
  // 12文字以上で大文字、小文字、数字、特殊文字を含む
  const length = 14;
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+';
  let password = '';
  
  // 各種類の文字を確実に含める
  password += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[Math.floor(Math.random() * 26)];
  password += 'abcdefghijklmnopqrstuvwxyz'[Math.floor(Math.random() * 26)];
  password += '0123456789'[Math.floor(Math.random() * 10)];
  password += '!@#$%^&*()-_=+'[Math.floor(Math.random() * 14)];
  
  // 残りをランダムに生成
  for (let i = 4; i < length; i++) {
    password += chars[Math.floor(Math.random() * chars.length)];
  }
  
  return password.split('').sort(() => 0.5 - Math.random()).join('');
}

// メイン処理
async function processUsersFromCsv() {
  const graphClient = initializeGraphClient();
  const users = [];
  
  // CSVファイルを読み込む
  fs.createReadStream(csvFilePath)
    .pipe(csv())
    .on('data', (data) => users.push(data))
    .on('end', async () => {
      console.log(`${users.length}人のユーザー情報を読み込みました。`);
      
      // 一括登録処理
      for (const userData of users) {
        try {
          await createUser(graphClient, userData);
        } catch (error) {
          // エラーが発生しても処理を続行
          console.error('ユーザー登録エラー:', error.message);
        }
      }
      
      console.log('すべてのユーザー登録処理が完了しました。');
    });
}

// スクリプト実行
processUsersFromCsv().catch(error => {
  console.error('エラーが発生しました:', error);
});

csvファイルのフォーマット

displayName,userPrincipalName,mailNickname,jobTitle,department,usageLocation
山田太郎,yamada.taro@example.com,yamadataro,マネージャー,営業部,JP
佐藤花子,sato.hanako@example.com,satohanako,エンジニア,開発部,JP
鈴木一郎,suzuki.ichiro@example.com,suzukiichiro,デザイナー,マーケティング部,JP

使用方法、アプリケーション権限

結論から言うと、この段階で実装の7,8割は完了していました。

上記のコードを適用した後、Microsoft Entra IDのアプリ登録方法、権限の設定方法を知らなかったため、質問。

詳細な返答が返ってきました。

返答をもとにアプリの登録をし、必要な情報をコードに入力し、実行すると、

正常に動作しました。Microsoft Entra 管理センターで登録を確認。

csvファイルからの自動登録に成功しました!

スクリプトの修正

修正の指示として、下記のプロンプトを入力しました。

回答結果として、.envファイルの生成

スクリプトの修正案

require('dotenv').config();
const fs = require('fs');
const csv = require('csv-parser');
const { ClientSecretCredential } = require('@azure/identity');
const { Client } = require('@microsoft/microsoft-graph-client');
const { TokenCredentialAuthenticationProvider } = require('@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials');

// .env ファイルから設定を読み込む
const tenantId = process.env.TENANT_ID;
const clientId = process.env.CLIENT_ID;
const clientSecret = process.env.CLIENT_SECRET;
const csvFilePath = process.env.CSV_FILE_PATH || './users.csv';
const organizationDomain = process.env.ORGANIZATION_DOMAIN;
const defaultPassword = process.env.DEFAULT_PASSWORD;
const defaultUsageLocation = process.env.DEFAULT_USAGE_LOCATION || 'JP';
const forceChangePassword = process.env.FORCE_CHANGE_PASSWORD_NEXT_SIGNIN === 'true';

// Microsoft Graph クライアントの初期化
function initializeGraphClient() {
  const credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
  const authProvider = new TokenCredentialAuthenticationProvider(credential, {
    scopes: ['https://graph.microsoft.com/.default']
  });

  return Client.initWithMiddleware({
    authProvider: authProvider
  });
}

// ユーザーを作成する関数
async function createUser(graphClient, userData) {
  try {
    // ユーザー名部分を取得(@より前の部分)
    const username = userData.userPrincipalName.split('@')[0];
    
    // 組織のドメインでuserPrincipalNameを構築
    const correctedUPN = `${username}@${organizationDomain}`;
    
    const user = {
      accountEnabled: true,
      displayName: userData.displayName,
      mailNickname: userData.mailNickname || userData.displayName.replace(/\s/g, '').toLowerCase(),
      userPrincipalName: correctedUPN,
      passwordProfile: {
        forceChangePasswordNextSignIn: forceChangePassword,
        password: defaultPassword
      },
      jobTitle: userData.jobTitle || '',
      department: userData.department || '',
      usageLocation: userData.usageLocation || defaultUsageLocation
    };

    console.log(`ユーザー作成中: ${userData.displayName} (${correctedUPN})`);
    const result = await graphClient.api('/users').post(user);
    console.log(`ユーザー作成成功: ${result.displayName} (ID: ${result.id})`);
    return result;
  } catch (error) {
    console.error(`ユーザー作成エラー: ${userData.displayName}`, error);
    throw error;
  }
}

// メイン処理
async function processUsersFromCsv() {
  const graphClient = initializeGraphClient();
  const users = [];
  
  // 環境変数が正しく設定されているかチェック
  if (!tenantId || !clientId || !clientSecret || !organizationDomain || !defaultPassword) {
    throw new Error('.env ファイルに必要な設定が不足しています。TENANT_ID、CLIENT_ID、CLIENT_SECRET、ORGANIZATION_DOMAIN、DEFAULT_PASSWORDを設定してください。');
  }
  
  // CSVファイルを読み込む
  fs.createReadStream(csvFilePath)
    .pipe(csv())
    .on('data', (data) => users.push(data))
    .on('end', async () => {
      console.log(`${users.length}人のユーザー情報を読み込みました。`);
      
      // 一括登録処理
      for (const userData of users) {
        try {
          await createUser(graphClient, userData);
        } catch (error) {
          // エラーが発生しても処理を続行
          console.error('ユーザー登録エラー:', error.message);
        }
      }
      
      console.log('すべてのユーザー登録処理が完了しました。');
    });
}

// スクリプト実行
processUsersFromCsv().catch(error => {
  console.error('エラーが発生しました:', error);
});

適用し再度テストをしたところ、正常に動作しました。

実装のポイント

  • 環境変数を使用して設定を管理。セキュリティに配慮。
  • デフォルトパスワードを使用することで、管理を容易にする。

成果と学び

今回はGitHub Copilotを活用し、目的のスクリプトを作成することができました。

元々知識がなかったGraph APIを使用したスクリプトの実装を手軽に行うことができました。

※今回の実装は、強い権限の使用を前提とした実装となっているので、同じような開発をする際は、個人では行わず、必ずチームや組織と相談して行ってください。

また、プロンプトを入力する前に、自分が作ろうとしているアプリの要件についてまとめておくことも大事だと感じました。

今回は修正案を後から入力しましたが、もう少し具体的な指示ができていれば、少ないやり取りで完成できたかと思います。

そして、GitHub Copilotを使用することで、自分が使ったことがない技術やツールを使用するハードルが下がることを実感しました。

今回の開発を通して、GraphAPI、Microsoftサービスについての知識を深めることができました。

新しい技術を学ぶときは、「わからないけどとりあえず手を動かして作ってみる」という姿勢が大切だと思っています。

GitHub Copilotは、そんな新しい技術への挑戦をサポートしてくれるツールです。

このブログが、IT業界未経験の学生や社会人、プログラミング初心者、そして生成AIを使用したプログラミングに興味がある方々にとって、少しでも参考になれば嬉しいです。

サービス一覧 www.alterbooth.com cloudpointer.tech www.alterbooth.com