Powershell で Microsoft365 のライセンス状況を確認する

組織で Microsoft 365 を使っている場合に、ユーザにどのライセンスが割当たっているかを確認したい場合があります。Office365 の管理機能から、ライセンスの利用者の CSV ファイルをエクスポートできますが、PowerShell でスマートに確認したいケースもあります。

実際に試したスクリプトサンプルを備忘録もかねて記載します。

# 準備

まず、PowerShell のインストールとモジュールのインストールが必要になります。

## Powershell の準備

PowerShell は基本的にバージョン 7 で問題ないと思います。

https://docs.microsoft.com/ja-jp/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.2

ただし、MSOnline モジュールは 7 では使えません。MSOnline を使う場合は、Windows Terminal を使うか、Powershell 5 のインストールが必要です。

https://docs.microsoft.com/ja-jp/microsoft-365/enterprise/connect-to-microsoft-365-powershell?view=o365-worldwide
> PowerShell Core は、Windows PowerShell 用 Microsoft Azure Active Directory モジュールと、名前に Msol が含まれるコマンドレットをサポートしていません。

ハマりやすいので気を付けてください。

## モジュールの準備

Office365 で管理しているライセンス情報などを確認する際には、Microsoft Graph を使用します。PowerShell から使用するために、まずインストールを行います。

```powershell:Graph のインストール
Install-Module Microsoft.Graph -Scope CurrentUser
```
管理者アカウントがない場合、-Scope CurrentUser を付けると、自分の権限分だけのモジュールをインストールすることができます。
もし管理権限でインストールしたい場合は、-Scope CurrentUser を省略します。

インストール後、モジュールに含まれるコマンドを実行しようとしても、「The term is not regognized as a name of a cmdlet,・・・」（そんなコマンドは見つかりませんよ！）と表示される場合があります。この場合は、次のコマンドを実行しましょう。

```powershell:Graph のコマンドが見つからない場合
Import-Module Microsoft.Graph 
```

Import-Module は、インストールした Microsoft.Graph のファイルパスが、今の Powershell セッションで認識されるようにするコマンドです。

ちなみに、Install-Module と Import-Module の名前が似ていて混乱してしまうことがよくありますのでご注意ください。
ややこしや・・・

## Microsoft Graph への接続

Microsoft.Graph に接続します。引数の Scope には、テナントに紐づくライセンス情報を取得するために「Organization.Read.All」を指定します。また、ユーザーのライセンス情報取得・付与などを行うために「User.ReadWrite.All」を指定します。

```PowerShell:Microsoft Graph を利用するために接続
Connect-MgGraph -Scopes "Organization.Read.All","User.ReadWrite.All"
```

ブラウザが起動し、サインインの画面が表示されます。Microsoft の認証が正しく通ると、いよいよ Microsoft Graph が利用できるようになります。

# コマンドサンプル

### ライセンスリストの表示

ライセンスの情報を得るには次のコマンドを実行します。

```powershell:ライセンスリスト
Get-MgSubscribedSku -all
```
ライセンスリストには、次の項目が含まれます。

|項目名|内容|
|-----|----|
|Id   |テナントID と SkuId をつなげたもの|
|AppliesTo|適用される種別|
|CapabilityStatus|有効/無効|
|ConsumedUnits|割当済み数|
|SkuId|ライセンスに割り当てられたId|
|SkuPartNumber |SKU の管理名称|

### ユーザに割り当てられたライセンス

ここでは xxxx@ppp.co.jp さんのライセンスを見てみます。

```powershell:ライセンスリスト
Get-MgUserLicenseDetail -UserId "xxxx@ppp.co.jp"
```

|項目名|内容|
|-----|----|
|Id   |管理ID|
|SkuId|ライセンスに割り当てられたId|
|SkuPartNumber |SkuId の管理名称|

### ライセンスが割り当てられたユーザの一覧

あんまりスマートに記述できなかったのですが、こんな感じにすれば表示できます。

```powershell:ライセンスを持つユーザリスト
# 元データの取得
$licenses = Get-MgSubscribedSku -all
$users = Get-MgUser -Filter 'assignedLicenses/$count ne 0' -ConsistencyLevel eventual `
         -CountVariable licensedUserCount -All -Select UserPrincipalName,DisplayName,AssignedLicenses
$list=@()
# 出力用データの作成
foreach($user in $users){
  $line = @{UserPrincipalName = $user.UserPrincipalName}
  $line.DisplayName = $user.DisplayName
  foreach($userlicense in $user.AssignedLicenses){
    foreach($license in $licenses){
     if($license.SkuId -eq $userlicense.SkuId)
     {
       $line.SkuPartNumbers += $license.SkuPartNumber + "`t"
     }
    }
  }
  $list+=$line
}
# 出力
$list | Select-Object UserPrincipalName , DisplayName, SkuPartNumbers
```

一度、$list に格納しているので、Where-Object を使うと、Get-MgUserLicenseDetail とよく似たことができます。
ただ、$list には SkuPartNumber が入っているので、いちいち Get-MgSubscribedSku で Sku を確認する必要はありませんので、その分、分かりやすいかと思います。

```powershell:特定のユーザのライセンス割り当て状況
$list | Where-Object {$_.UserPrincipalName -eq "xxxx@ppp.co.jp"}
```

# 標準出力でファイルに出力すると右端が切れる場合の対処

標準出力では、Powershell のコンソール画面の設定幅をみて右端の表示範囲を決めています。このため、コンソール画面のウィンドウサイズを大きくすることで表示できる範囲を広げることができます。

PowerShell のコンソール画面のプロパティを開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177616/e7354d2a-292c-8f84-9461-338f40e789ed.png)

すると、設定画面が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177616/aff8bc95-2206-5601-41f3-2ed816e308b6.png)

レイアウトタブのウィンドウサイズの幅を大きくします。
割り当てライセンスの状況によりますので、出力を試しながら、適切な範囲を探ってみてください。

## 次行でもいいから全部出す

次行に渡ってもいいから、全部を表示したい場合は、ft コマンドを使用します。ft コマンドに -Wrap を指定することで改行がおこなわれ、全ての項目が表示されます。

```powershell:出力例
$list | Select-Object UserPrincipalName , DisplayName, SkuPartNumbers | ft -Wrap
```