Intuneのデバイス登録をSlackに通知させてみた

はじめに

こんにちわ、ヤスムラです。

先日、以下の記事でGemini Canvasを使ったツール作成を紹介しましたが、今回はその時にGeminiさんに作成してもらった「Intuneのデバイス登録をSlackに通知するツール」の完成版スクリプトと設定方法を紹介したいと思います。

事前準備

設定には、以下のアカウント(権限)が必要です。

項目実施すること
Microsoft Entra の管理者権限API連携用のアプリ登録と権限付与
Slackの管理者権限 通知用Webhookの追加
GoogleアカウントGASの実行

設定手順

手順は以下の5ステップです。

  1. 手順①:Microsoft Entraにアプリケーション登録
  2. 手順②:Slackアプリの追加
  3. 手順③:GASのコード入力
  4. 手順④:GASへ設定値を入力
  5. 手順⑤:初回実行とトリガーの設定

手順①:Microsoft Entra にアプリケーション登録

Intuneの情報へアクセスするための「アプリケーション」を登録します。
1. Microsoft Entra管理センター にサインイン
2. 左メニューの [ID] > [アプリケーション] > [アプリの登録] >[+ 新規登録] をクリック


3. 以下のように入力し [登録] をクリック

項目内容
名前わかりやすい名前
(Intune Slack Notifier など)
サポートされているアカウントの種類この組織のディレクトリ内のアカウントのみ
リダイレクト URI:空のまま

4.登録完了後、概要ページで以下の2つの値をコピーして控えておいて下さい。

  • アプリケーション (クライアント) ID
  • ディレクトリ (テナント) ID

5. 左メニューから [管理] > [証明書とシークレット] > [+ 新しいクライアントシークレット] をクリック

6. 説明を入力し、有効期限を選択して [追加] をクリック
7. 作成したシークレットの値をコピーして控えておいて下さい。

8. 左メニューから [API のアクセス許可] > [+ アクセス許可の追加] をクリック

9. [Microsoft Graph] > [アプリケーションの許可] をクリック
10. 検索ボックスに DeviceManagementManagedDevices.Read.All と入力
11. 表示された権限にチェックを入れ、[アクセス許可の追加] をクリック

12.構成されたアクセス許可にて[(テナント名)に管理者の同意を与えます]をクリック

13. 状態に✅マークが表示されていることを確認

手順②:Slackアプリの追加

Slackへ通知するためのWebhook URLを取得します。
1. Slack APIのアプリ管理ページ[Create New App] > [From scratch] を選択

2.アプリ名と対象ワークスペースを選択し [Create App] をクリック

3. [Incoming Webhooks] をクリックし、機能を有効(On)にする

4. [Add New Webhook] をクリックして通知チャンネルを選択し[許可する] をクリック
5. 生成された Webhook URL (https://hooks.slack.com/...) をコピーして控えておいて下さい。

手順③:GASのコード入力

GASプロジェクトを作成し、スクリプトを貼り付けます。

  1. Google Apps Scriptで「新しいプロジェクト」を作成
  2. プロジェクト名にわかりやすい名前を設定「Intune Slack通知など」
  3. 初期コードを全て削除し、以下のコードを貼り付けて保存
/**
 * Microsoft Intuneに登録された新しいデバイスを検知し、Slackに通知するGoogle Apps Script。
 */

// --- 設定はスクリプトプロパティで行います ---
// (設定項目は変更ありません)
//
// SLACK_WEBHOOK_URL   : SlackのIncoming Webhook URL
// AZURE_CLIENT_ID     : Azure AD アプリケーション (クライアント) ID
// AZURE_CLIENT_SECRET : Azure AD クライアントシークレット
// AZURE_TENANT_ID     : Azure AD ディレクトリ (テナント) ID
// ------------------------------------------A

// スクリプトプロパティに前回実行時間を保存するためのキー
const PROPERTY_KEY_LAST_CHECK_TIME = 'LAST_CHECK_TIME_ISO';

/**
 * アプリケーション認証を使い、Microsoft Graph APIのアクセストークンを取得します。
 */
function getAccessToken(config) {
  const tokenUrl = `https://login.microsoftonline.com/${config.azureTenantId}/oauth2/v2.0/token`;
  
  const payload = {
    'grant_type': 'client_credentials',
    'client_id': config.azureClientId,
    'client_secret': config.azureClientSecret,
    'scope': 'https://graph.microsoft.com/.default'
  };

  const options = {
    'method': 'post',
    'payload': payload,
    'muteHttpExceptions': true
  };
  
  try {
    const response = UrlFetchApp.fetch(tokenUrl, options);
    const result = JSON.parse(response.getContentText());
    
    if (response.getResponseCode() !== 200) {
      console.error('アクセストークンの取得に失敗しました。', result);
      return null;
    }
    
    return result.access_token;
  } catch (e) {
    console.error('アクセストークン取得中に例外が発生しました。', e);
    return null;
  }
}

/**
 * メインの処理を実行する関数。
 */
function checkNewDevices() {
  const scriptProperties = PropertiesService.getScriptProperties();
  const config = {
    slackWebhookUrl: scriptProperties.getProperty('SLACK_WEBHOOK_URL'),
    azureClientId: scriptProperties.getProperty('AZURE_CLIENT_ID'),
    azureClientSecret: scriptProperties.getProperty('AZURE_CLIENT_SECRET'),
    azureTenantId: scriptProperties.getProperty('AZURE_TENANT_ID')
  };

  // 設定値のチェック
  for (const key in config) {
    if (!config[key] && key !== 'slackWebhookUrl') { // slackWebhookUrlは通知時にのみ必須
       console.error(`スクリプトプロパティ「${key.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`).toUpperCase()}」が設定されていません。`);
       return;
    }
  }

  // 毎回新しいアクセストークンを取得
  const accessToken = getAccessToken(config);
  if (!accessToken) {
    console.error('処理を中断しました: アクセストークンが取得できませんでした。');
    return;
  }

  // --- 以下、時間ベースのチェックロジック---
  const executionTime = new Date();
  const executionTimeISO = executionTime.toISOString();
  const lastCheckTimeISO = scriptProperties.getProperty(PROPERTY_KEY_LAST_CHECK_TIME);

  if (!lastCheckTimeISO) {
    scriptProperties.setProperty(PROPERTY_KEY_LAST_CHECK_TIME, executionTimeISO);
    console.log('初回実行のため、基準時間を設定しました。次回から新規デバイスのチェックを開始します。基準時間:', executionTimeISO);
    return;
  }
  
  try {
    const lastCheckTime = new Date(lastCheckTimeISO);
    console.log(`チェックを開始します。対象期間: ${lastCheckTimeISO} 以降`);

    const url = 'https://graph.microsoft.com/v1.0/deviceManagement/managedDevices';
    const response = UrlFetchApp.fetch(url, {
      headers: { 'Authorization': `Bearer ${accessToken}` },
      muteHttpExceptions: true
    });

    const result = JSON.parse(response.getContentText());
    if (response.getResponseCode() !== 200) {
      console.error('Graph APIからのデバイスリスト取得に失敗しました。', result);
      return;
    }

    const allDevices = result.value;
    if (!allDevices || allDevices.length === 0) {
      console.log('Intuneに登録されているデバイスはありませんでした。');
      scriptProperties.setProperty(PROPERTY_KEY_LAST_CHECK_TIME, executionTimeISO);
      return;
    }

    const newDevices = allDevices.filter(device => {
      const enrolledDateTime = new Date(device.enrolledDateTime);
      return enrolledDateTime > lastCheckTime;
    });

    if (newDevices.length > 0) {
      console.log(`${newDevices.length}件の新しいデバイスが見つかりました。`);
      newDevices.forEach(device => {
        sendSlackNotification(device, config.slackWebhookUrl);
      });
    } else {
      console.log('新しいデバイスは見つかりませんでした。');
    }
    
    scriptProperties.setProperty(PROPERTY_KEY_LAST_CHECK_TIME, executionTimeISO);
    console.log('チェック完了。次回の基準時間を更新しました:', executionTimeISO);

  } catch (e) {
    console.error('スクリプトの実行中にエラーが発生しました。', e);
  }
}

/**
 * 指定されたデバイス情報をSlackに通知します。
 */
function sendSlackNotification(device, webhookUrl) {
  if (!webhookUrl) {
    console.error('Slack Webhook URLが設定されていません。通知をスキップします。');
    return;
  }
  const enrolledDateTimeJST = new Date(device.enrolledDateTime).toLocaleString('ja-JP');

  const message = {
    text: `新しいデバイスがIntuneに登録されました: ${device.deviceName}`,
    blocks: [
      {
        type: 'header',
        text: { 'type': 'plain_text', 'text': ':computer: 新規デバイス登録通知', 'emoji': true }
      },
      {
        type: 'section',
        fields: [
          { 'type': 'mrkdwn', 'text': `*デバイス名:*\n${device.deviceName}` },
          { 'type': 'mrkdwn', 'text': `*所有者:*\n${device.managedDeviceOwnerType}` },
          { 'type': 'mrkdwn', 'text': `*登録日時:*\n${enrolledDateTimeJST}` },
          { 'type': 'mrkdwn', 'text': `*ユーザー:*\n${device.userPrincipalName || 'N/A'}` },
          { 'type': 'mrkdwn', 'text': `*OS:*\n${device.operatingSystem || 'N/A'}` },
          { 'type': 'mrkdwn', 'text': `*機種名:*\n${device.model || 'N/A'}` },
          { 'type': 'mrkdwn', 'text': `*シリアル番号:*\n${device.serialNumber || 'N/A'}` }
        ]
      },
      { 'type': 'divider' },
      {
        'type': 'context',
        'elements': [
          { 'type': 'mrkdwn', 'text': `デバイスID: ${device.id}` }
        ]
      }
    ]
  };
  const options = {
    'method': 'post',
    'contentType': 'application/json',
    'payload': JSON.stringify(message),
    'muteHttpExceptions': true
  };
  UrlFetchApp.fetch(webhookUrl, options);
}

/**
 * スクリプトを定期実行するためのトリガーを設定します。
 */
function setTrigger() {
  const triggers = ScriptApp.getProjectTriggers();
  triggers.forEach(trigger => {
    if (trigger.getHandlerFunction() === 'checkNewDevices') {
      ScriptApp.deleteTrigger(trigger);
    }
  });

  ScriptApp.newTrigger('checkNewDevices')
    .timeBased()
    .everyHours(1)
    .create();
  
  console.log('1時間ごとの定期実行トリガーを設定しました。');
}

/**
 * 基準時間をリセットします。(デバッグ用)
 */
function resetLastCheckTime() {
  PropertiesService.getScriptProperties().deleteProperty(PROPERTY_KEY_LAST_CHECK_TIME);
  console.log('基準時間をリセットしました。');
}

手順④:GASへ設定値を入力

手順①と②で取得した情報をGASのスクリプトプロパティに設定します。

1. GASエディタで [プロジェクトの設定] (⚙️アイコン) をクリック
2. [スクリプト プロパティ] セクションで [スクリプト プロパティを追加] をクリック
3.以下の4つの情報を追加

プロパティ (Property)値 (Value)
AZURE_CLIENT_ID手順①で取得したクライアントID
AZURE_CLIENT_SECRET手順①で取得したクライアントシークレット
AZURE_TENANT_ID手順①で取得したテナントID
SLACK_WEBHOOK_URL手順②で取得したSlackのWebhook URL

手順⑤:初回実行とトリガーの設定

スクリプトを実行して初回実行日時の取得と自動実行のトリガーを設定します。

1. GASエディタで checkNewDevices 関数を選択し、[▷ 実行]
2. (初回のみ)権限の承認ポップアップが表示されるので[権限を確認]をクリックして許可

3.ログに以下メッセージが表示されることを確認

4.setTrigger 関数を選択して [▷ 実行]
5.ログに「1時間ごとの定期実行トリガーを設定しました。」と表示されれば全設定が完了です。

動作確認

すべての設定が完了し、ツールが正常に動作すると、Intuneに新しいデバイスが登録された際に、以下のような通知がSlackへ自動で届きます。

補足

  • 初回実行の役割: 手順⑤で最初に checkNewDevices を実行すると、その実行時刻が「基準時間」としてスクリプトプロパティに記録されます。スクリプトは2回目以降の実行で、この記録された時間よりも後に登録されたデバイスを「新規」と判断します。
  • 過去登録したデバイスを通知したい: テスト等で過去のデバイスを再度通知させたい場合は、GASのスクリプトプロパティにて「LAST_CHECK_TIME_ISO」に登録されている時間を手動修正すれば実施可能です。

さいごに

今回は、Intuneの新規デバイス登録をGASでSlackへ自動通知する仕組みを紹介しました。
この設定で日々の確認作業が不要になり、デバイス登録をより効率的に把握できます。業務効率化の一助となれば幸いです。

さいごに当方は副業情シスとして、中小企業様を中心に社内ITの最適化をご支援しております。もし、「社内にIT担当者がいなくて困っている」「今のIT環境、もっと良くできるはず…」といったお悩みを抱える企業様がいらっしゃいましたら、ぜひお声がけください。下記サイトに詳細を記載しておりますので、ご興味をお持ちいただけましたら、問い合わせページよりお気軽にご連絡いただけますと幸いです。

Corporate-Engineer

コメント

タイトルとURLをコピーしました