SaaS

Intuneでカスタムインベントリの収集?技術的には可能です。。。を解説!

IDチームのすかんくです。今回は「Intuneってインベントリ機能弱いよねー」という声が沢山聞こえてきたので、プロアクティブな修復機能を用いて Windows 環境でカスタムインベントリを収集する方法についてご紹介したいと思います。

タイトルにもある通り “技術的には可能” であり、”実運用面では中々に無理がありそう” な部分を含めて解説できたらと思います。

ではいきましょう。

検討の背景

2023/03 現在、Microsoft Intuneではデバイスからカスタムインベントリの収集をサポートしていません。
また、Microsoft 365 プロダクト全体で見ても、収拾可能なハードウェア及びソフトウェアインベントリの量は限られており、組織固有の条件に基づいたインベントリを柔軟に追加したりするための機能は存在していません。(Microsoft Endpoint Cofiguration Manager を例としたオンプレ資産管理系を除く)

資産管理ツールを例としたスケジュールに従ってデバイスから広範なインベントリを収集したり、レポーティングすることに慣れているユーザーほど、Intune のレポート機能は不十分であると感じると思います。

これは、「そもそも Intune は資産管理ツールではない」ことが根底にありますが、Intune 利用しているユーザーにおいては実務上どうにかしなければならない場面があるよね?という所から、今回の検証がスタートしています。

やりたいこと

  • Intune でカスタムインベントリを出力する
  • カスタムインベントリを Graph API から参照する

Intune でカスタムインベントリを出力する

前提条件

  • プロアクティブな修復が利用可能であること
  • Intune から [エンドポイント分析] プロファイル が配布済みであること
  • ユーザーに Windows Enterprise E3/E5/A3/A5 ライセンスが割り当たっていること
  • Windows 10 以降の Enterprise、Professional、または Education のいずれかのエディションが実行されていること
  • Azure AD Join または Hybrid Azure AD Join されていること
  • パッケージ要件(サンプルを掲示するため、一旦は気にしなくて良いです)
    • スクリプトは UTF-8 でエンコードされていること
    • 出力は 2048 文字(character)以下であること
    • 登録可能なパッケージ(スケジューラ)は 200 まで
    • 多すぎると端末動作に支障が出る可能性あり

手順

①Windows 機に対して、[エンドポイント分析] プロファイル を配布します

②インベントリ収集に利用するパッケージを作成します
※今回は Windows のローカルユーザーで “Administrators” グループに所属するユーザーの一覧を取得してみます
※後段でスクリプトの解説をしています
※以下のスクリプトをそのままコピーして UTF-8 形式でエンコードした PS1 ファイルを作成してください

サンプル:ローカル管理者の取得

######################################################################
# Get inventory | Detect Local Admin
######################################################################
$LocalUserArray = @()

$group = get-wmiobject win32_group -filter "name='Administrators'" -ErrorAction SilentlyContinue
$group.GetRelated("win32_useraccount") | ForEach-Object {
	$tempLocalUser = new-object -TypeName PSObject
	$tempLocalUser | Add-Member -MemberType NoteProperty -Name "LocalUserName" -Value $_.Name -Force
	$tempLocalUser | Add-Member -MemberType NoteProperty -Name "LocalUserType" -Value "Administrator" -Force
	$LocalUserArray += $tempLocalUser
}

$group = get-wmiobject win32_group -filter "name='Users'"
$group.GetRelated("win32_useraccount") | ForEach-Object {
	$tempLocalUser = new-object -TypeName PSObject
	$tempLocalUser | Add-Member -MemberType NoteProperty -Name "LocalUserName" -Value $_.Name -Force
	$tempLocalUser | Add-Member -MemberType NoteProperty -Name "LocalUserType" -Value "User" -Force
	$LocalUserArray += $tempLocalUser
}
[System.Collections.ArrayList]$LocalUserArrayList = $LocalUserArray



######################################################################
# Convert to JSON
######################################################################
$LocalUserArrayListJson = $LocalUserArrayList | ConvertTo-Json -Compress



######################################################################
# Output 
######################################################################
If ($LocalUserArrayListJson.Length -gt 2048)
{
    Write-Output "Output is longer than the permitted length of 2048 characters."
    Exit 1
}else 
{
    Write-Output $LocalUserArrayListJson
    Exit 0
}

Intune管理センターを開き、[レポート] > [エンドポイント分析] > [プロアクティブな修復] > [+スクリプト パッケージの作成] をクリックします

④ [名前/説明] に適切な情報を入力し、[次へ] をクリックします

⑤ [検出スクリプト ファイル] に作成したスクリプトをアップロードします

⑥ [64 ビットの PowerShell でスクリプトを実行する] を “はい” にして、[次へ] をクリックします

⑦ 任意のグループへ割り当てを行い、展開スケジュールを指定します
※ 初期割り当てはテスト用グループの指定を推奨
※ 今回は1日1回のスケジュールで設定

⑧ 各設定を確認の上、[作成] をクリックします。

以上で設定は完了です。
サンプルスクリプトをそのまま利用している場合、Adminsitartors グループは以下のユーザーリストが取得できるようになりました。

動作確認

テスト機に対してここまでの手順で作成したプロファイルや、パッケージが割り当たるように準備を行い、動作を確認しましょう。
※サンプルスクリプトをそのまま利用している場合、テスト用のローカルユーザーを作成して Administrators へ追加しておくと、より良いかと思います。

Intune管理センターから、作成したパッケージの [デバイスの状態] を開きます。

② [列] をクリックし、[修復前の検出エラー] 及び [修復目の検出の出力] にチェックを入れて [適用] をクリックします。

③ パッケージが無事実行されると、「修復前の検出の出力] に Local Admin ユーザーの一覧が json 形式で出力されていることが確認できます。
※ 何らかの理由でパッケージが正常終了しなかった場合、エラーメッセージが [修復前の検出エラー] に表示されます。

コラム:スクリプトの解説

サンプルとして掲示したスクリプトは [Get Inventory/Convert to JSON/Output] の3 ブロックに分かれています。
それぞれのブロックについて、簡単に解説を載せておきます。

注意!

筆者は PowerShell スクリプトについてそこまで詳しいわけではなく、より良い記述の仕方や処理として不十分な内容となっている可能性がある点にご留意ください。

[Get Inventory]
・取得したいインベントリを PowerShell で取得し、事前定義した Array 型に追加しています

[Convert to JSON]
・取得した Array 型の変数を Json 形式にコンバートする
・Json 形式で出力が不要(単要素)なインベントリの場合、この処理はスキップしてもOKです
・API 側で出力を取得した際の形式が統一されるので、Json 形式で統一しておくと吉

[Output]
・2048 文字以上の出力でないことを確認して、出力の後に終了する
・Write-Output で出力したテキストがパッケージの [修復前の検出の出力] へ記録される
・Exit 1 が異常系、Exit 0 が正常系として Intune へリターンされる
・エラーによって実行が終了した場合、エラーメッセージがパッケージの [修復前の検出エラー] に記録される

解説は以上ですが、その他サンプルについても添えておきます。

サンプル:Disk のリスト取得

######################################################################
# Get inventory | Disks
######################################################################
$DiskArray = @()
$Disks = Get-PhysicalDisk -ErrorAction SilentlyContinue | Where-Object { $_.BusType -match "NVMe|SATA|SAS|ATAPI|RAID" }
	
foreach ($Disk in ($Disks | Sort-Object DeviceID)) {
	# Obtain disk health information from current disk
	$DiskHealth = Get-PhysicalDisk -UniqueId $($Disk.UniqueId) | Get-StorageReliabilityCounter | Select-Object -Property Temperature, TemperatureMax

	# Obtain media type
	$DriveDetails = Get-PhysicalDisk -UniqueId $($Disk.UniqueId) | Select-Object MediaType, HealthStatus
	$DriveMediaType = $DriveDetails.MediaType
	$DriveHealthState = $DriveDetails.HealthStatus
	$DiskTempDelta = [int]$($DiskHealth.Temperature) - [int]$($DiskHealth.TemperatureMax)

	# Create custom PSObject
	$DiskState = new-object -TypeName PSObject

	# Create disk entry
	$DiskState | Add-Member -MemberType NoteProperty -Name "DiskNumber" -Value $Disk.DeviceID
	$DiskState | Add-Member -MemberType NoteProperty -Name "FriendlyName" -Value $($Disk.FriendlyName)
	$DiskState | Add-Member -MemberType NoteProperty -Name "MediaType" -Value $DriveMediaType
	$DiskState | Add-Member -MemberType NoteProperty -Name "LogicalSectorSize" -Value $Disk.LogicalSectorSize
	$DiskState | Add-Member -MemberType NoteProperty -Name "SerialNumber" -Value $Disk.SerialNumber

	$DiskArray += $DiskState	
}
[System.Collections.ArrayList]$DiskArrayList = $DiskArray


######################################################################
# Convert to JSON
######################################################################
$DiskArrayListJson = $DiskArrayList | ConvertTo-Json -Compress



######################################################################
# Output 
######################################################################
If ($DiskArrayListJson.Length -gt 2048)
{
    Write-Output "Output is longer than the permitted length of 2048 characters."
    Exit 1
}else 
{
    Write-Output $DiskArrayListJson
    Exit 0
}

サンプル:NIC の取得

######################################################################
# Get inventory | NetworkAdapters
######################################################################
$NetWorkArray = @()
$CurrentNetAdapters = Get-NetAdapter -ErrorAction SilentlyContinue | Where-Object { $_.Status -eq 'Up' }
foreach ($CurrentNetAdapter in $CurrentNetAdapters) {
	try{
		$IPConfiguration = Get-NetIPConfiguration -InterfaceIndex $CurrentNetAdapter[0].ifIndex -ErrorAction Stop
	}
	catch{
		$IPConfiguration = $null
	}
	$ComputerNetInterfaceDescription = $CurrentNetAdapter.InterfaceDescription
	$ComputerNetProfileName = $IPConfiguration.NetProfile.Name
	$ComputerNetIPv4Adress = $IPConfiguration.IPv4Address.IPAddress
	$ComputerNetInterfaceAlias = $CurrentNetAdapter.InterfaceAlias
	$ComputerNetIPv4DefaultGateway = $IPConfiguration.IPv4DefaultGateway.NextHop
	$ComputerNetMacAddress = $CurrentNetAdapter.MacAddress
	
	$tempnetwork = New-Object -TypeName PSObject
	$tempnetwork | Add-Member -MemberType NoteProperty -Name "NetInterfaceDescription" -Value "$ComputerNetInterfaceDescription" -Force
	$tempnetwork | Add-Member -MemberType NoteProperty -Name "NetProfileName" -Value "$ComputerNetProfileName" -Force
	$tempnetwork | Add-Member -MemberType NoteProperty -Name "NetIPv4Adress" -Value "$ComputerNetIPv4Adress" -Force
	$tempnetwork | Add-Member -MemberType NoteProperty -Name "NetInterfaceAlias" -Value "$ComputerNetInterfaceAlias" -Force
	$tempnetwork | Add-Member -MemberType NoteProperty -Name "NetIPv4DefaultGateway" -Value "$ComputerNetIPv4DefaultGateway" -Force
	$tempnetwork | Add-Member -MemberType NoteProperty -Name "MacAddress" -Value "$ComputerNetMacAddress" -Force
	$NetWorkArray += $tempnetwork
}
[System.Collections.ArrayList]$NetWorkArrayList = $NetWorkArray



######################################################################
# Convert to JSON
######################################################################
$NetWorkArrayListJson = $NetWorkArrayList | ConvertTo-Json -Compress



######################################################################
# Output 
######################################################################
If ($NetWorkArrayListJson.Length -gt 2048)
{
    Write-Output "Output is longer than the permitted length of 2048 characters."
    Exit 1
}else 
{
    Write-Output $NetWorkArrayListJson
    Exit 0
}

カスタムインベントリを Graph API から参照する

ここまで、Intune 管理画面内でカスタムインベントリを収集し、表示する手順を解説してきました。
しかし一般的なユースケースとしては、外部のレポートサービスや管理台帳に反映させたいということが良くあると思います。

なのでここからは、Intune に記録されたカスタムインベントリを Graph API で取得する方法ついて紹介していきたいと思います。

注意

今回ご紹介する手順は Graph Exploler からの取得における手順となります。
そのため、実際に外部サービスと連携させる際は、他にも細かなタスクが多々あるという点にご留意ください。

例:bearer tokenの取得フロー、jsonのparse処理など

前提条件

  • 管理者アカウント
  • Microsoft Graph API の利用
    • アプリケーションのアクセス許可(DeviceManagementConfiguration.Read.All)

手順

① 作成したパッケージにアクセスし、URLの “ID/” 以降の GUID を控えます

Graph Exploler にアクセスし、画面右上のアイコンから管理アカウントでログインします

③ API バージョンを “beta” に変更し、URL 部に以下を入力します

  • https://graph.microsoft.com/beta/deviceManagement/deviceHealthScripts/{手順①で取得したGUID}/deviceRunStates

④ [Modify permissions] タブに移動し、必要なアクセス許可を与えます

⑤ [Run query] をクリックし、[Response preview] から “preRemediationDetectionScriptOutput” プロパティにインベントリ情報が格納されていることを確認します。

以上で手順は完了です、Graph API でもカスタムインベントリを取得できることが確認できました。

必要に応じて、外部サービスから API を呼び出し既存のレポートに反映することが可能となります。

「技術的には可能」について

ここまで、あたかも「Intune でカスタムインベントリの収集が可能」という体で紹介してきましたが、冒頭で記載したように実運用を行うとなった場合には中々難しいのではないかと考えています。

以下に何故そう考えるかについて箇条書きで記載します。

  • Windows 10/11 Enterprise 前提なので、利用できる組織は限られる
  • パッケージの作成には、PowerShell の理解がある程度必要
  • 登録できるパッケージが 200 までなので、組織部門によってパッケージを分けていたら企業規模によっては破綻する
  • 出力が 2048 文字(character)以下なので、リスト化された情報量が増えすぎた場合には対応が難しい
  • 外部のレポートや資産管理台帳と連携するには、Graph API の理解がある程度必要
  • jsonが改行を含まない形式で出力されているので、purse処理を書くのが単純に面倒
  • 既存のデバイス情報と突合する際に、今回紹介した API ではデバイス名は拾えないため追加の API 取得が恐らく必要

等々、、、挙げればきりがないです。

本当に「極一部のデバイスやユーザーに対し」、「極一部のインベントリ情報だけを取得する」といった極めて限られたユースケースでないと実運用は難しいのではないかというのが正直な感想になります。
つまりは「技術的には可能」ですが、「そこまでのリソースを掛けて構築するだけの価値があるのか?」という点においては否定的な回答を持つ方が大多数ではないかと思います。

組織や事業によって、特定のカスタムインベントリを取得されるケースはあると思いますが、そういった際にはやはりそれ専用のサービスを個別に検討するのが現状としては必要になってくるのかなと、今回の検証を通じて感じました。

こうだったらいいな、Intune

「Jamf Pro を見習って、気軽に拡張属性を追加して動的グループで扱えるようにしてほしい。」これに尽きます。

それ以上語ることが特に無いので、賛同していただける同志は是非サービスへフィードバックやリクエストを挙げましょう。(チラッ

ユーザーの声が大きければ、プロダクトも真剣に検討してくれるかもしれませんね。

まとめ

今回は Intune でカスタムインベントリを取得する方法と、実運用は難しいのではないかという点について解説してきました。
プロアクティブな修復機能は他にも様々な使い道があると思いますので、各組織に合った利用方法を検討してみるのも面白いかもしれませんね。

ではまた!

すかんく

2022/1 入社、Identity チームのすかんくと申します。
ブログでは IdP 関連の機能紹介を中心に記載していこうと思います。
好きな漫画はアイシールド21・ハイキュー・ベイビーステップです。