LoginSignup
3
3

はじめに

社内雑用係として約30年程勤しむ亀 行道(かめ ゆきみち)と申します。
約2年程前にですが、タイトルにある様な作業が必要になり、当初は緊急性もあり社内営業担当へ手順書を作成してOutlook予定表へのアクセス権を自分(以降作業を実施する関係者すべて)に付与、ExcelのVAB機能で社内営業担当のOutlook予定表情報を一括収集した後、CSV保存してSQL Serverへアプロードし整形や情報付与、再度ですがExcelのPower Query機能でSQL整形後のOutlook予定表情報をインポートして、ピボットテーブルでビジュアル化して社内へ情報を提供していた次第です。
然しながら、上記作業は完全に個人依存となっており、自分以外に社内にはプログラム開発経験者が居ない上に採用の見込みも無い、他社員へのVBAやSQLなど複数言語やA5:SQLの利用方法等の引継ぎの難しさ等もあり、バッチ処理でOutlook予定表情報を取得してSQL Serverへアプロードなど作業省力化して、PHP等でOutlook予定表情報をビジュアル化して社内へ提供する方向へ舵を切ろうとしていました。
そこで、PHPでのOutlook予定表情報をビジュアル化に関しては、時間さえあれば解決でき(そう...)ますが、このOutlook予定表情報を取得する方法について、当初は安易にVisualStudioにてVisualBasicで開発を検討していましたが、将来性も鑑みVisualStudioのC#で実装しようと試みたところ、古い情報が多く最新状態で動作する事例が少なく、特に認証周り(Azure.Identity)やOutlook予定表情報へアクセスする為のAPI(Microsoft.Graph)等の情報が少なく四苦八苦していましたが、どうにかOutlook予定表情報を取得する事に成功したので、現在ですが小生の様に苦しんでいる方々への助力になればと思い足跡を残す次第です。
なお、当ソースコードではあくまでも情報を取得して、コンソールに情報を吐くだけでSQL Serverへの連携や実務レベルの例外処理などは実装せず、シンプルになっていますので、本番環境で実装する際には、各自実装いただければ幸いです。

前提条件

  • Azure Potalを利用できる管理者権限

実装環境

  • エディター : Microsoft Visual Studio Community 2022 Version 17.10.1
  • 開発言語 : C#
  • 利用API
    • Microsoft.Graph Ver 5.56.0
    • Azure.Identity Ver 1.11.4

当仕組みによるメリット

  • Outlook予定表へのアクセス権付与が不要
  • Outlook予定表情報の社内公開作業の大幅省力化
  • 作業属人化の排除(言い訳は巻末で...)

全体の流れ

1.Azule Potalにて、アプリの追加
2.Azule Potalにて、アプリで利用するAPIのメソッドと権限の追加
3.Azule Potalにて、シークレットの追加
4.VisualStudioにて、C#コードを記述

操作手順

1.Azure Potalへログイン
2.左上のハンバーグメニューマークをクリック
20240617a.jpg
2.展開したメニューから「Microsoft Enter ID」をクリック
20240617b.jpg
3.メニューから「アプリの登録」をクリック
20240617c.jpg
4.「新規登録」をクリックします。
20240617d.jpg
5.以下項目を設定します。ここでは「名前」(当例では「OutlookAccess001」としています。)と「サポートされているアカウントの種類」を設定します。
20240617e.jpg
6.画面下部の「登録」ボタンをクリックします。
20240617y.jpg
7.後で必要になるので、次をメモ帳などに控えます。「アプリケーション(クライアント)ID」と「ディレクトリ(テナント)ID」をメモ帳に控えたら、「APIのアクセス許可」をクリックします。
20240617f.jpg
8.「アクセス許可の追加」をクリックします。
20240617g.jpg
9.「Microsoft Graph」をクリックします。
20240617i.jpg
10.「アプリケーションの許可」をクリックします。
20240617h.jpg
11.以下の画面が開いたら画面下部にある「アクセス許可を選択」から下へスクロールして「Calendars」を探す。
20240617j.jpg
12.以下「Calendars」を展開して「Calendars.Read」、「Calendars.ReadBasic.All」、「Calendars.ReadWrite」のチェックボックスにチェックを入れます。
20240617k.jpg
13.画面下部にある「アクセス許可の追加」をクリックします。
20240617l.jpg
14.画面戻ったら、「(自分の会社名)に管理者の同意を与えます」をクリックします。
20240617m.jpg
15.以下の様に「状態」から警告が外れることを確認します。
20240617n.jpg
16.「証明書とシークレット」をクリックします。
20240617o.jpg
17.「新しいクライアント シークレット」をクリックします。
20240617p.jpg
18.「説明」(当例では「OutlookAccess001」としています。)、「有効期限」(ここでは、最大値の「推奨:180日(6か月)」を選択しています。)を設定します。その後、「追加」ボタンをクリックします。
20240617q.jpg
19.後で必要になるので、「値」の配下の値をメモ帳などに控えます。
20240617r.jpg
20.2回目以降は以下の様に値がマスクされるのでご注意ください。
20240617s.jpg
21.以上でAzure Potalでの作業は終了です。
22.以降は「Visual Studio」での作業ですが詳細は省略します。
23.プロジェクトは必ず「C# コンソールアプリ」で、プロジェクト名は「OutlookAccess001」とします。
20240617t.jpg
24.ソースコードは以下のとおりです。貼り付けすれば大丈夫なはず...

using Microsoft.Graph;
using Azure.Identity;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Graph.Models;
using System.Reflection;
using Microsoft.Kiota.Abstractions;
using Microsoft.Graph.Models.CallRecords;

namespace OutlookAccess001
{
	 class Program
	{
		private const string clientId = "アプリケーション(クライアント)ID";
      	private const string tenantId = "ディレクトリ(テナント)ID";
		private const string clientSecret = "クライアントシークレットの値";
		private const string userId = "Outlook予定表を取得したい社員のメールアドレス";

		static async Task Main(string[] args)
		{
			try
			{
				// クライアントシークレット資格情報を作成
				var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);

				// GraphServiceClientを初期化
				var graphClient = new GraphServiceClient(clientSecretCredential);

				// 日本時間(JST)の開始日と終了日を指定
				TimeZoneInfo jstZone = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
				DateTime startDateTimeJst = new DateTime(2024, 6, 1, 0, 0, 0, DateTimeKind.Unspecified);
				DateTime endDateTimeJst = new DateTime(2024, 6, 30, 23, 59, 59, DateTimeKind.Unspecified);

				// JSTをUTCに変換
				DateTime startDateTimeUtc = TimeZoneInfo.ConvertTimeToUtc(startDateTimeJst, jstZone);
				DateTime endDateTimeUtc = TimeZoneInfo.ConvertTimeToUtc(endDateTimeJst, jstZone);

				// 開始日と終了日をISO 8601形式に変換
				string startDateTime = startDateTimeUtc.ToString("o");
				string endDateTime = endDateTimeUtc.ToString("o");

				// 共有または委任された予定表のイベントを取得
				var events = await graphClient.Users[userId]
					.Calendar
					.CalendarView
					.GetAsync((config) =>
					{
						config.QueryParameters.Top = 999;
						config.QueryParameters.StartDateTime = startDateTime;
						config.QueryParameters.EndDateTime = endDateTime;
						config.QueryParameters.Select = new string[] { "subject", "start", "end", "location", "attendees", "organizer", "body", "bodyPreview" };
						config.QueryParameters.Orderby = new string[] { "start/dateTime" };
					});

				// イベントの詳細をコンソールに表示
				var index = 1;
				foreach (var eventItem in events.Value)
				{


					var StartDateTimeJst = TimeZoneInfo.ConvertTimeFromUtc(DateTime.Parse(eventItem.Start.DateTime), jstZone);
					var EndDateTimeJst = TimeZoneInfo.ConvertTimeFromUtc(DateTime.Parse(eventItem.End.DateTime), jstZone);

					Console.WriteLine($"利用者: {userId}");						   // 利用者
					Console.WriteLine($"No.: {index}");							 // No.
					Console.WriteLine($"件名: {eventItem.Subject}");				// 件名
					Console.WriteLine($"開始日時: {StartDateTimeJst}");			  // 開始日時
					Console.WriteLine($"終了日時: {EndDateTimeJst}");			   // 終了日時
					Console.WriteLine($"場所: {eventItem.Location.DisplayName}");   // 場所
					Console.WriteLine($"開催者: {eventItem.Organizer.EmailAddress.Address}");   // 開催者
					Console.WriteLine($"本文: {eventItem.BodyPreview}");					  // 本文

					Console.WriteLine("必須参加者 : ");
					foreach (var attendee in eventItem.Attendees)
					{
						if (attendee.Type == AttendeeType.Required)
						{
							Console.WriteLine($" - {attendee.EmailAddress.Address}");
						}
					}

					Console.WriteLine("任意参加者 : ");
					foreach (var attendee in eventItem.Attendees)
					{
						if (attendee.Type == AttendeeType.Optional)
						{
							Console.WriteLine($" - {attendee.EmailAddress.Address}");
						}
					}
					// 改行
					Console.WriteLine();
					index++;
				}
			}
			catch (Azure.Identity.AuthenticationFailedException ex)
			{
				Console.WriteLine($"Authentication failed: {ex.Message}");
			}
			catch (Microsoft.Graph.Models.ODataErrors.ODataError ex)
			{
				Console.WriteLine($"OData error: {ex.Error.Message}");
			}
			catch (Exception ex)
			{
				Console.WriteLine($"An error occurred: {ex.Message}");
			}
		}
	}
}

25.ソース貼り付け後、右ペインにあるプロジェクトで右クリックして、メニューから「NuGetパッケージの管理」をクリックします。
20240617v.jpg
26.以下のパッケージをインストールします。
20240617u.jpg

27.上記にある実行ボタンをクリックして挙動を確認します。
20240617w.jpg
26.以下の様にコンソールが開いて、実行結果が表示されます。
20240617x.jpg
27.以上となります。

終わりに

小生ですが小学校3年から不登校もあり小学校卒業程度の知能しかなく、中卒ですので上記記事には正しくない文体や稚拙な日本語あるとは思いますがご理解いただければ幸いです。記事に嘘はないはず...
また、実は小生は数年前から難病(全身の毛細血管が劣化して出血します。また、2時間位立っていると足回り、特に脹脛あたりは皮下出血も伴い恥ずかしいです。同時に内臓でも発生しているで貧血で倒れるのも厄介です。夕方になると出血が上半身まで広がり、2の腕とかまで登ってくると吊革につかまるのが恥ずかしいです。)となり、平時出勤が難しく在宅勤務が増えており、上記Excel VBA作業などが困難でもあり何とか自動化する必要があると思い取り組んだ次第です。
当件ですが別の意味で作業属人化していると誰もが思うでしょうが、そこは察して頂ければ幸いです。
いつも「Qiita」にある皆様の投稿記事には助けられていますので少しでも恩返しがしたく稚拙ながらも記事を投稿させて頂いた次第です。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3