こんにちは、コーポレートチームの宮川です。以前の記事で、IntuneとJamfをどう活用しているかについて紹介しました。今回は、それらのMDMの機能を活用してPC管理業務を効率化をした事例について紹介します。


Intune APIとJamf APIから取得したデータをGAS(Google Apps Script)を用いて、Googleスプレッドシートにデータを書き込むことで、資産管理の効率化を実現しました。具体的な実現方法と、それによって資産管理業務にどのような効果があったのかを紹介します。
従来のスプレッドシートにおける資産管理
機器管理台帳について
コーポレートチームでは、購入したPCのデータのレコードを入れていくスプレッドシートを機器管理台帳と命名しています。機器管理台帳は表形式で、主に以下の項目(列)で構成されています。
ホスト名、在庫(倉庫)、割当て日、使用者、備考、シリアル番号 など
PC管理において特に重要なのは、ホスト名、状態(在庫/倉庫)、使用者の3項目です。
- ホスト名は、Windowsの「デバイス名」やMacの「コンピュータ名」に相当し、一定のルールに基づいて命名しています。例えば、「L24-001」のように命名し、「L」はLaptopの意味であり、Macの場合はM、デスクトップの場合はDにしています。続く数字で、購入年と連番が分かるようになっています。
- 在庫(倉庫)は、PCがその時にどのような状態か記録しておくための項目です。倉庫保管、貸出中、廃棄予定、廃棄済みなどの状態があります。
- 使用者は、そのPCを使っている人を記入します。使用者が変更になったり、PCが返却されたりした際には、都度更新が必要です。
機器管理台帳の問題点
この手動更新による機器管理台帳には、いくつか問題点がありました。
- 手作業での更新のため、編集漏れが発生しても気づきにくい点です。私たちのチーム(コーポレートチーム3名)のように複数人で編集する場合、この問題はより顕著になります。
- 機器管理台帳の情報だけでは、端末がリアルタイムで実際に使用されているかどうかが分かりません。実際のログイン状況などは、各MDM(Intune, Jamf)の管理コンソールで確認する必要があります。そのため、台帳とMDMコンソールを往復して情報を確認する手間が発生していました。
- ユーザーがWindowsのデバイス名やMacのコンピュータ名を変更してしまうと、MDM上に表示されるホスト名と機器管理台帳のホスト名が一致しなくなり、MDM側から特定の端末を検索しにくくなる点です。特にMacでは、AirDropで表示されるのがコンピュータ名であるため、AirDropを頻繁に利用する部署では、分かりやすい名前に変更したいというニーズがありました。
今回は、これらの問題点を解決する実装をしました。
実装
Intune APIとJamf APIからGAS(Google Apps Script)を用いて、スプレッドシートに表示させることにしました。前述の問題点を解決するため、各APIから最低限必要となる情報は以下の通りです。
- シリアル番号
- ユーザー名
- 最終ログイン時刻
機器管理台帳のスプレッドシート内に、Jamf用とIntune用のデータ取得結果を表示するシートをそれぞれ別途作成します。
今回の実装におけるデータの流れは以下です。
Intune APIによるデータの取得
GASプロジェクトを作成し、以下のコードを書きます。
/**
* Microsoft Graph API からアクセストークンを取得する
*/
function getAccessToken() {
// Microsoft Graph API の認証情報
const TENANT_ID = PropertiesService.getScriptProperties().getProperty("tenant_id");; // Azure AD のテナントID
const CLIENT_ID = PropertiesService.getScriptProperties().getProperty("client_id"); // Azure AD のクライアントID
const CLIENT_SECRET = PropertiesService.getScriptProperties().getProperty("client_secret");; // Azure AD のクライアントシークレット
const tokenUrl = `https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token`;
const payload = {
grant_type: "client_credentials",
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
scope: "https://graph.microsoft.com/.default"
};
const options = {
method: "post",
payload: payload
};
const response = UrlFetchApp.fetch(tokenUrl, options);
const json = JSON.parse(response.getContentText());
Logger.log('アクセストークンを取得しました')
return json.access_token;
}
/**
* Microsoft Graph API から Intune のデバイス情報を取得する
*/
function getIntuneDevices() {
// Microsoft Graph API のエンドポイント
const GRAPH_API_URL = "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices";
const accessToken = getAccessToken();
const options = {
method: "get",
headers: {
Authorization: `Bearer ${accessToken}`,
accept: 'application/json',
},
};
try {
const response = UrlFetchApp.fetch(GRAPH_API_URL, options);
const data = JSON.parse(response.getContentText());
//Logger.log(data)
Logger.log('intuneの情報を取得しました')
return data.value
} catch(e) {
// 例外エラー処理
Logger.log('Error:')
Logger.log(e)
}
}
/**
* スプレッドシートにデバイス情報を書き込む
*/
function writeToSpreadsheet() {
const SHEET_ID = PropertiesService.getScriptProperties().getProperty("sheet_id")
const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName('sheet_name')
sheet.clear(); // 既存のデータをクリア
// ヘッダーを設定
const headers = ["シリアル番号","管理デバイス名","最終同期時刻","デバイス名","メールアドレス(UPN)","メーカー", "モデル" ,'準拠しているか(60日ログインなし)'];
sheet.appendRow(headers);
// デバイス情報を取得
const devices = getIntuneDevices();
// デバイス情報を情報を二次元配列にする
const records = []
devices.forEach(device => {
records.push([
device.serialNumber || "N/A",
device.managedDeviceName || "N/A",
device.lastSyncDateTime || "N/A",
device.deviceName || "N/A",
device.userPrincipalName || "N/A",
device.manufacturer || "N/A",
device.model || "N/A",
device.complianceState || "N/A"
])
});
sheet.getRange(2,1,devices.length,8).setValues(records)
Logger.log("スプレッドシートにデータを書き込みました");
}Azule ADの管理コンソールからアプリの登録をします。
そして、Azule ADのテナントID、登録したアプリのクライアントID、クライアントシークレットを取得して、対象のGASプロジェクトのスクリプトプロパティに追加すれば完了です。
Jamf APIによるデータの取得
GASプロジェクトを作成し、以下のコードを書きます。
function sendJamfDataToSpreadsheet() {
const token = getToken()
const computer_inventry = readComputers(token)
const serialNumber = getSerialNumber(token)
outputSpreedSheet(computer_inventry["results"],serialNumber)
}
/**
* JAMF APIからアクセストークンを取得する
*/
function getToken() {
// https://developer.jamf.com/jamf-pro/docs/client-credentials
const url = `https://prtimes.jamfcloud.com/api/oauth/token`;
const options = {
method: 'post',
payload: {
grant_type: 'client_credentials',
client_id: PropertiesService.getScriptProperties().getProperty('JAMF_CLIENT_ID'),
client_secret: PropertiesService.getScriptProperties().getProperty('JAMF_CLIENT_SECRET')
}
};
try {
const response = UrlFetchApp.fetch(url, options);
const content = JSON.parse(response.getContentText('UTF-8'));
Logger.log('アクセストークンを取得')
return content.access_token;
} catch {
Logger.log('Error:')
Logger.log(e)
}
}
/**
* JAMF APIからデバイス情報を取得する
*/
function readComputers(token){
const options = {
method: 'get',
headers: {
Authorization: `Bearer ${token}`,
accept: 'application/json',
},
};
const baseURL = 'https://prtimes.jamfcloud.com/api/v1/computers-inventory?section=GENERAL&page=0&page-size=100&sort=general.name';
const response = UrlFetchApp.fetch(baseURL,options)
Logger.log('コンピュータインベントリの取得')
const obj = JSON.parse(response)
return obj
}
/**
* スプレッドシートに表示する
*/
function outputSpreedSheet(results,serialNumberObjects){
const wSpread = SpreadsheetApp.openById(PropertiesService.getScriptProperties().getProperty('EQUIPMENT_MANAGEMENT_LEDGER'));
const wSheet = wSpread.getSheetByName(PropertiesService.getScriptProperties().getProperty('JAMF_SHEET'));
wSheet.clear();
let i = 2
for(result of results){
wSheet.getRange(i, 1).setValue(serialNumberObjects[result["id"]]);
wSheet.getRange(i, 2).setValue(result["general"]['name']);
wSheet.getRange(i, 3).setValue(result["general"]["mdmCapable"]["capableUsers"][0]);
wSheet.getRange(i, 4).setValue(result["general"]["reportDate"]);
i = i + 1
}
}
/**
* シリアル番号とIDを紐付けたオブジェクトを取得
*/
function getSerialNumber(token){
const baseUrl = `https://prtimes.jamfcloud.com/api/preview/computers?page=0&page-size=100&sort=name%3Aasc`;
const options = {
method: 'get',
headers: {
Authorization: `Bearer ${token}`,
accept: 'application/json',
},
};
try {
const response = UrlFetchApp.fetch(baseUrl,options)
const objects = JSON.parse(response)
const id_serialNumber = new Object()
for (object of objects["results"]){
id_serialNumber[object['id']] = object['serialNumber']
}
Logger.log('シリアル番号とidのオブジェクトの取得')
return id_serialNumber
} catch {
Logger.log('Error:')
Logger.log(e)
}
}
Jamfで、APIロールとAPIクライアントの設定をします。
そこで取得した、クライアントIDとクライアントシークレットを、対象のGASプロジェクトのスクリプトプロパティに追加すれば完了です。
機器管理台帳への表示
Intune/Jamfからのデータ取得が自動化できたら、次は元の機器管理台帳シートにこれらの情報を統合し、確認作業の手間を減らします。
GoogleスプレッドシートのVLOOKUP関数などを用いて、シリアル番号をキーにしてIntune/Jamfのデータ取得用シートから関連情報を参照します。これにより、機器管理台帳シート上で各PCのMDM上の最新情報(使用者名、最終接続時刻など)をまとめて確認できるようになります。
例:IFERROR(VLOOKUP(R2,’Intuneデータ表示用’!$A$2:$C$500,2,FALSE),””)
作成したGASの関数は、トリガー機能を使って定期的に自動実行させることができます。GASエディタの左メニューにある時計アイコン(トリガー)から「トリガーを追加」をクリックし、実行したい関数を設定します。以下と同様の設定をすると、毎週月曜日の9時から10時に実行されるようになります。
この実装における業務改善
この仕組みにより、冒頭で挙げた手動更新による問題点を解消できました。加えて、実装・運用する中で副次的な効果や改善点も見えてきました。
- Jamf Proは登録デバイス数に応じてライセンス費用が発生するため、クライアントを管理コンソールから削除する必要があります。機器管理台帳で返却済みなどのステータスになっているにも関わらず、Jamfのデータ取得シートに情報が残り続けている場合、Jamf Proからの削除漏れに気づきやすくなりました。
- Intuneには、コンプライアンスに準拠しているか(デフォルトだと30日)という項目があり、ログインしないで30日経過すると、コンプライアンスに準拠していない状態になります。これを取得・表示することで、長期間使用されていない可能性のある端末を容易に特定できるようになりました。
まとめ
今回は、GAS(Google Apps Script)とMDM(Intune/Jamf)のAPI連携により、コーポレートチームの資産管理の効率化を実現しました。
従来、端末の最新状況を確認するために、機器管理台帳(スプレッドシート)と各MDMの管理コンソールを個別に参照する必要がありましたが、この手間が大幅に削減されました。
個人的には日々のオペレーションを楽にしたり、簡略化できることは嬉しいので、これからもそのような貢献はできないか探して行動していきたいと思います。

