Microsoft Graph PowerShell で Azure AD / Microsoft Entra ID のグループ管理を自動化しようとしたとき、「New‑MgGroupMember」と「New‑MgGroupMemberByRef」というよく似たコマンドが出てきて戸惑うことがあります。本記事では、この2つの違いと使い分けを、Graph REST API の仕組みから実務での書き方・サンプルスクリプトまで丁寧に解説します。
New‑MgGroupMember と New‑MgGroupMemberByRef の違いとは?
まず最初に一番大事なポイントを押さえておきます。
- どちらも「グループにメンバーを追加する」ことが目的
- 違うのは「指定方法」と「抽象度(どれだけ生の Graph に近いか)」
イメージしやすいように、2つのコマンドの位置づけを簡単に表にしてみます。
| 項目 | New‑MgGroupMember | New‑MgGroupMemberByRef |
|---|---|---|
| 目的 | グループにメンバーを追加 | グループにメンバーを追加 |
| 指定方法 | Directory Object の ID をそのまま渡す | @odata.id を含む JSON を 自分で組み立てて渡す |
| 抽象度 | ラッパー(書きやすい高レベル API) | Graph REST の $ref をほぼそのまま叩く低レベル API |
| 内部的な挙動 | 内部で @odata.id を組み立てて /members/$ref に POST | 渡したボディをそのまま /members/$ref に POST |
| 書きやすさ | ◎(短くて読みやすい) | ○(OData を意識する必要あり) |
| 汎用性 | △(グループメンバー追加に特化) | ◎(Graph の他の $ref 操作と統一しやすい) |
| 向いている場面 | 日常的なスクリプト/簡単な自動化 | Graph の $ref を明示して扱いたいとき、既に URL を持っているとき |
どちらを使っても最終的に「そのユーザー(またはサービスプリンシパル、デバイスなど)がグループのメンバーになる」という結果は同じです。あとは、書きやすさを取るか、Graph REST に近い表現を取るかの違いだと考えてください。
New‑MgGroupMember の基本と特徴
もっとも簡単に書ける “ラッパー” コマンド
New‑MgGroupMember は、グループにメンバーを追加するための「書きやすいラッパー」です。必要なのは、
- グループの ID(
-GroupId) - 追加したいオブジェクトの ID(
-DirectoryObjectId)
だけです。実際の使用例はこんなイメージです。
# 例:ユーザーを 1 人グループに追加する
$groupId = "00000000-0000-0000-0000-000000000000"
$objectId = "11111111-1111-1111-1111-111111111111" # ユーザーや SPN の ObjectId
New-MgGroupMember -GroupId $groupId -DirectoryObjectId $objectId
内部では、SDK が自動的に次のような JSON を組み立てています。
{
"@odata.id": "https://graph.microsoft.com/v1.0/directoryObjects/11111111-1111-1111-1111-111111111111"
}
そして、Graph REST API の POST /groups/{group-id}/members/$ref に対してこの JSON を送信する、という処理をやってくれています。
DirectoryObjectId に渡せるオブジェクト
-DirectoryObjectId パラメータには、directoryObject に属する ID を渡すことができます。代表的なものは次の通りです。
- ユーザー(
user) - グループ(ネストされたグループ)
- サービス プリンシパル(アプリケーション)
- デバイス
つまり、「Azure AD / Entra ID 上のディレクトリオブジェクトであれば基本的に対象になる」と理解すると分かりやすいです。
実務でよくあるシンプルな例
グループ名とユーザーのメールアドレス(UPN)だけを知っているケースを想定したサンプルです。
# グループ名とユーザーの UPN から ID を取得して追加する例
$group = Get-MgGroup -Filter "displayName eq 'Marketing'" -ConsistencyLevel eventual -Count groupCount
$user = Get-MgUser -UserId "[email protected]"
New-MgGroupMember -GroupId $group.Id -DirectoryObjectId $user.Id
たったこれだけで OK です。Graph の URL や @odata.id などを意識する必要はありません。
New‑MgGroupMemberByRef の基本と特徴
Graph REST とほぼ 1 対 1 の “素の” コマンド
New‑MgGroupMemberByRef は、REST API でいう /$ref への POST を、ほぼそのまま PowerShell から叩くためのコマンドです。
使い方の基本形は次のようになります。
$groupId = "00000000-0000-0000-0000-000000000000"
$objectId = "11111111-1111-1111-1111-111111111111"
$params = @{
'@odata.id' = "https://graph.microsoft.com/v1.0/directoryObjects/$objectId"
}
New-MgGroupMemberByRef -GroupId $groupId -BodyParameter $params
自分で @odata.id を組み立てる必要がある点が、先ほどの New‑MgGroupMember との大きな違いです。その代わり、Graph REST API の仕様に詳しい人ほど、動きが読みやすくなります。
REST API に近い書き方が欲しい場面で便利
New‑MgGroupMemberByRef のメリットは、
- Graph Explorer や REST API ドキュメントに載っている
/$refの例とほぼ同じ形で記述できる - 他の
xxxByRefコマンド(New‑MgGroupOwnerByRefなど)とパターンを揃えられる - 既に別処理で取得済みの
@odata.idをそのまま使い回せる
といった点です。「Graph の $ref を、スクリプト全体で統一的に扱いたい」というポリシーで運用している環境では、ByRef 系だけを使う、という書き方もよく見かけます。
なぜ 2 つのコマンドが存在するのか(設計の背景)
Graph REST API の「関連付け」はほぼすべて $ref パターン
Microsoft Graph の REST API では、ユーザーとグループ、グループと所有者、アプリとサービスプリンシパル…といった多くの「関連付けの追加」が、共通して次のようなパターンになっています。
POST /{resource}/{id}/{navigationProperty}/$ref
Content-Type: application/json
{
"@odata.id": "https://graph.microsoft.com/v1.0/{ターゲットのリソースパス}/{ターゲットのID}"
}
つまり、「何かと何かを紐づける」という操作は、ほぼ全部 @odata.id を渡して /$ref に POST する、という共通ルールで実装されています。
PowerShell SDK は REST をそのまま映した「ByRef 系」と、書きやすい「ラッパー系」を自動生成している
Microsoft Graph PowerShell SDK は、Graph のメタデータから自動生成されています。その際に、多くのエンドポイントに対して次のような 2 種類のコマンドが生成される仕組みになっています。
xxxByRef:REST の/$refをほぼそのまま叩く低レベル APINew‑MgGroupMemberのようなラッパー系:よく使うパターンを ID 直渡しでシンプルに書ける高レベル API
そのため、結果はまったく同じでも、
- 「とにかく手早く書きたい・読みやすさ重視」 → ラッパー系(
New‑MgGroupMember) - 「Graph の REST を理解したうえで統一的に扱いたい」 →
ByRef系(New‑MgGroupMemberByRef)
というように、チームのスタイルや将来の拡張性に応じて選べるようになっているわけです。
実務での使い分けパターン
ここからは、「どんなときにどちらを選ぶとラクか?」という観点で具体的に見ていきます。
| シナリオ | おすすめコマンド | ポイント |
|---|---|---|
| 単にユーザーをグループに追加したいだけ | New‑MgGroupMember | ID をそのまま渡すだけでよく、コードが短く読みやすい |
たくさんの /$ref 操作を 1 つのスクリプトで扱う | New‑MgGroupMemberByRef | 所有者追加やアプリの関連付けなどと同じ “ByRef パターン” に揃えられる |
別の API から @odata.id を受け取ってそれを流用したい | New‑MgGroupMemberByRef | @odata.id をそのまま -BodyParameter に渡せるため変換が不要 |
| PowerShell 初心者にも読めるスクリプトにしたい | New‑MgGroupMember | OData/REST の知識がなくても意味を理解しやすい |
| Graph REST ドキュメントと 1 行 1 行照らし合わせて検証したい | New‑MgGroupMemberByRef | REST の POST /groups/{id}/members/$ref と完全に対応している |
パターン 1:とにかく簡単にメンバー追加したい(New‑MgGroupMember)
# CSV に列 UserPrincipalName, GroupName がある想定
$users = Import-Csv ".\members.csv"
foreach ($row in $users) {
$group = Get-MgGroup -Filter "displayName eq '$($row.GroupName)'" -ConsistencyLevel eventual -Count groupCount
$user = Get-MgUser -UserId $row.UserPrincipalName
New-MgGroupMember -GroupId $group.Id -DirectoryObjectId $user.Id
}
Graph の URL を意識しなくてよいので、Active Directory 時代の cmdlet に近い感覚で扱えます。
パターン 2:/$ref 統一で書きたい(New‑MgGroupMemberByRef)
グループの メンバー も 所有者 も同じような書き方に揃えたい場合は、ByRef 系に統一しておくとスクリプトがスッキリします。
$group = Get-MgGroup -Filter "displayName eq 'Marketing'"
$user = Get-MgUser -UserId "[email protected]"
$baseUrl = "https://graph.microsoft.com/v1.0"
$params = @{
'@odata.id' = "$baseUrl/directoryObjects/$($user.Id)"
}
# メンバー追加
New-MgGroupMemberByRef -GroupId $group.Id -BodyParameter $params
# 所有者追加(参考)
# New-MgGroupOwnerByRef -GroupId $group.Id -BodyParameter $params
メンバー追加でも所有者追加でも、同じ $params を使い回せるので、「関連付け」処理の見通しが良くなるのが利点です。
使用例(最小構成):両者の書き方比較
同じ処理を 2 通りで書いた最小の例を並べてみます。
New‑MgGroupMember 版
$groupId = "00000000-0000-0000-0000-000000000000"
$objectId = "11111111-1111-1111-1111-111111111111"
New-MgGroupMember -GroupId $groupId -DirectoryObjectId $objectId
New‑MgGroupMemberByRef 版
$groupId = "00000000-0000-0000-0000-000000000000"
$objectId = "11111111-1111-1111-1111-111111111111"
$params = @{
'@odata.id' = "https://graph.microsoft.com/v1.0/directoryObjects/$objectId"
}
New-MgGroupMemberByRef -GroupId $groupId -BodyParameter $params
どちらも実行結果は同じです。New‑MgGroupMember は 1 行少ない代わりに内部で URL が組み立てられている、と理解するとよいでしょう。
追加後の確認方法
メンバー追加後に、実際にグループに反映されているか確認するには、Get‑MgGroupMember を使用します。
# グループメンバーを確認
Get-MgGroupMember -GroupId $groupId |
Select-Object Id, UserPrincipalName, DisplayName
サービス プリンシパルやデバイスが含まれる場合は、UserPrincipalName が空になることもあるため、必要に応じて Get-MgUser / Get-MgServicePrincipal と組み合わせて詳細を取得するようにしましょう。
実務で押さえておきたい補足・注意点
対象オブジェクトの種類
どちらのコマンドでも、追加できるのは directoryObject に属するオブジェクトです。具体的には次のようなものがあります。
- ユーザー(User)
- グループ(Group:ネストされたグループ)
- サービス プリンシパル(Service principal)
- デバイス(Device)
「この ID を渡してよいかな?」と迷ったら、まずは Get-MgDirectoryObject -DirectoryObjectId <ID> でオブジェクトの種類を確認すると安心です。
所有者の追加は別コマンドを使う
グループの「メンバー」と「所有者」は別の概念です。所有者を追加したい場合は、次のコマンドを使用します。
New-MgGroupOwnerNew-MgGroupOwnerByRef
書き方や考え方は本記事で解説した New‑MgGroupMember / New‑MgGroupMemberByRef とほぼ同じですので、セットで覚えておくと便利です。
動的グループには手動でメンバーを追加できない
Azure AD / Microsoft Entra ID の「動的メンバーシップ」を使っているグループは、ルールによって自動的にメンバーが決まるため、これらのコマンドでメンバーを追加しようとするとエラーになります。
- 事前にグループの メンバーシップの種類(Assigned / DynamicUser / DynamicDevice) を確認する
- 動的グループの場合は、対象ユーザーの属性(部署、役職、部門コードなど)を変更するスクリプトに切り替える
といった運用にしておくと、トラブルを避けやすくなります。
必要な権限(Graph API の権限と管理ロール)
スクリプトを実行するアカウントには、少なくとも以下のような権限が必要です。
- Graph のアプリケーション/委任権限として
Group.ReadWrite.Allなどのグループ書き込み権限 - 管理ロールとしては、グループ管理やユーザー管理を許可されたロール(例:グループ管理者、ユーザー管理者、全体管理者 など)
特に、アプリケーションとして実行する場合(非対話のサービスアカウントなど)は、管理者同意(Admin consent) が必要になることが多いため、事前にテナント管理者と調整しておきましょう。
すでにメンバーだった場合の挙動(冪等性の注意)
対象のオブジェクトがすでにグループのメンバーになっている状態で再度追加を実行すると、環境によっては次のような挙動になります。
- 「すでに存在する」という趣旨のエラーが返る
- 例外が投げられスクリプトが停止する
冪等(べきとう)ではないので、バッチ処理や定期実行ジョブでは注意が必要です。安全に実装するには、次のようなパターンを推奨します。
function Add-MgGroupMemberIfNotExists {
param(
[Parameter(Mandatory)] [string] $GroupId,
[Parameter(Mandatory)] [string] $ObjectId
)
$exists = Get-MgGroupMember -GroupId $GroupId -All | Where-Object Id -eq $ObjectId
if (-not $exists) {
New-MgGroupMember -GroupId $GroupId -DirectoryObjectId $ObjectId
}
}
# 利用例
Add-MgGroupMemberIfNotExists -GroupId $group.Id -ObjectId $user.Id
このように小さな関数にしておくと、どのスクリプトから呼び出しても安全に追加できるようになります。
エラー処理とログ出力のポイント
本番環境での運用を考えると、エラーの種類をある程度区別してログに残すのがおすすめです。
- 403 Forbidden:権限不足
- 404 Not Found:グループ ID / オブジェクト ID の誤り or 削除済み
- 429 Too Many Requests:スロットリング(短時間に大量リクエスト)
try {
New-MgGroupMember -GroupId $groupId -DirectoryObjectId $objectId -ErrorAction Stop
Write-Host "Added: $objectId to $groupId"
}
catch {
Write-Warning "Failed to add $objectId to $groupId : $($_.Exception.Message)"
}
この程度でもよいので、最低限のエラーハンドリングを入れておくと、「どこまで処理が進んだのか」を後から追跡しやすくなります。
New‑MgGroupMember / ByRef を使った一括追加シナリオ
CSV からの一括追加(New‑MgGroupMember 版)
実務でよくある「CSV をインポートして一括追加」のパターンです。
# members.csv の例
# GroupName,UserPrincipalName
# Marketing,[email protected]
# Marketing,[email protected]
$rows = Import-Csv ".\members.csv"
foreach ($row in $rows) {
$group = Get-MgGroup -Filter "displayName eq '$($row.GroupName)'" -ConsistencyLevel eventual -Count groupCount
$user = Get-MgUser -UserId $row.UserPrincipalName
if (-not $group) {
Write-Warning "Group not found: $($row.GroupName)"
continue
}
if (-not $user) {
Write-Warning "User not found: $($row.UserPrincipalName)"
continue
}
Add-MgGroupMemberIfNotExists -GroupId $group.Id -ObjectId $user.Id
}
ここでは先ほどの Add-MgGroupMemberIfNotExists を活用して、二重追加を防いでいます。
REST 仕様に揃えた一括追加(New‑MgGroupMemberByRef 版)
同じ処理を ByRef 版で書くと次のようになります。
$baseUrl = "https://graph.microsoft.com/v1.0"
$rows = Import-Csv ".\members.csv"
foreach ($row in $rows) {
$group = Get-MgGroup -Filter "displayName eq '$($row.GroupName)'" -ConsistencyLevel eventual -Count groupCount
$user = Get-MgUser -UserId $row.UserPrincipalName
if (-not $group -or -not $user) { continue }
$params = @{
'@odata.id' = "$baseUrl/directoryObjects/$($user.Id)"
}
try {
New-MgGroupMemberByRef -GroupId $group.Id -BodyParameter $params -ErrorAction Stop
Write-Host "Added (ByRef): $($user.UserPrincipalName)"
}
catch {
Write-Warning "Failed (ByRef): $($user.UserPrincipalName) : $($_.Exception.Message)"
}
}
@odata.id の組み立てという 1 ステップが増える代わりに、「REST API を直接叩く場合とほぼ同じ考え方でスクリプトを読める」というメリットがあります。
New‑MgGroupMember / ByRef に関するよくある疑問
Q. パフォーマンスの違いはある?
両者とも最終的には同じ REST API(/groups/{id}/members/$ref)を叩いているため、パフォーマンスに大きな差はありません。ボトルネックになるのは、
- メンバー追加前の
Get-MgGroup/Get-MgUserなどの検索 - 1 度に大量のメンバーを追加することによるスロットリング
などであり、コマンドの種類というよりは全体の設計・実装の仕方に依存します。
Q. Azure AD Graph や古いモジュールからの移行ではどちらを使うべき?
旧来の AzureAD モジュール(Add-AzureADGroupMember など)から移行する場合、まずは 書き方が近い New‑MgGroupMember を使うほうがスムーズです。REST の URL や @odata.id を気にせずに置き換えられるケースが多いからです。
その上で、Graph の $ref 操作全体を深く理解したくなったら、New‑MgGroupMemberByRef も試してみる、というステップがおすすめです。
Q. どちらか一方だけ覚えればいい?
日常的な運用だけなら、正直なところ New‑MgGroupMember だけでも困ることはほとんどありません。ただし、
- Graph REST API のドキュメントと照らし合わせながらスクリプトを読み解きたい
- 所有者追加・アプリの関連付けなど他の
/$ref操作もガッツリ触る予定がある
といった方は、ByRef 系コマンドも押さえておくと、理解の幅が広がります。
まとめ:日常は New‑MgGroupMember、設計志向なら ByRef も
最後に、本記事のポイントを整理します。
- New‑MgGroupMember と New‑MgGroupMemberByRef は、どちらも「グループにメンバーを追加する」コマンド
- 違いは主に、ID をそのまま渡すか、
@odata.idを自分で組み立てるかという指定方法と抽象度 New‑MgGroupMemberは書きやすいラッパーで、日常的なスクリプトに最適New‑MgGroupMemberByRefは Graph REST の/$refをほぼそのまま叩く低レベル API で、統一的な設計や REST ドキュメントとの対応を重視する場合に便利- 実務では、冪等性(すでにメンバーの場合の扱い)・動的グループの扱い・必要権限 といった周辺の注意点もあわせて押さえておくことが重要
結論として、
- 普段使い:
New‑MgGroupMemberで十分 - Graph の
$refを明示的に扱いたい/設計を揃えたい:New‑MgGroupMemberByRefを採用
という整理で覚えておけば、Microsoft Graph PowerShell でのグループメンバー管理はかなりスムーズになるはずです。自分やチームのスクリプトスタイルにあわせて、ぜひ両者を使い分けてみてください。

コメント