GWSアカウントの一元管理運用（GASでのミニマム管理）

# はじめに
GoogleWorkSpaceアカウントの管理を行う必要があったため、一元管理の方法をまとめました。
アカウント管理は手作業で行うと、意外と時間と手間がかかります。
アカウントの作成、削除、情報の更新などを自動化して、アカウント管理を効率的に行う事を目的に、スプレッドシート上での管理を実装してみます。

# 準備
### APIの有効化
GASからAdminDirectory SDKの実行をする場合、事前にAdmin SDK APIの有効化が必要。
`管理コンソール >> APIとサービス >> APIとサービスを有効にする`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3949968/77a695fe-9718-5818-d0ad-4c3ba036751c.png)

### AppScript設定
#### サービスの追加
GASのスクリプトエディタの「サービス」から、「AdminDirectory」を追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3949968/1bc45e9b-e9bd-d119-1339-1fedd52dd3e9.png)

#### プロジェクト設定
プロジェクト設定から、Google Cloud Platformのプロジェクト番号を追加


# 実装
### アカウント情報の取得
アカウント情報のメールアドレスを取得して、スプレッドシートに表示。
AdminDirectory.Users.list()でユーザーに関する情報を取得し、今回はgivenName、familyName、primaryEmailを表示します。givenNameでソートをかけてます。
[https://admin.googleapis.com/admin/directory/v1/users](https://developers.google.com/admin-sdk/directory/reference/rest/v1/users/list?hl=ja)


```javascript:gws_get.gs
function getUsers(){
  let pageToken;
  let page;
  let result = [];
  
  do {
    page = AdminDirectory.Users.list({
      domain: 'YOUR_DOMAIN',
      orderBy: 'givenName',
      maxResults: 500,
      pageToken: pageToken
    });
    let users = page.users;
    if (users) {
      users.forEach(x=> result.push([x.name.familyName,x.name.givenName,x.primaryEmail]))
    }
    pageToken = page.nextPageToken;
  }while (pageToken)
  
  const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheet1");
  sh.clear();
  SpreadsheetApp.flush();
  const menu = ["姓","名","Mail"];
  result.unshift(menu);
  sh.getRange(1,1,result.length,result[0].length).setValues(result);      
  SpreadsheetApp.flush();
}
```
実行するとスプレッドシートに取得したアカウントリストが表示されます。
実行したアカウントに認証を求められるので、下記どちらかのスコープが付いている事に注意。
`https://www.googleapis.com/auth/admin.directory.user
https://www.googleapis.com/auth/admin.directory.user.readonly`

### アカウントの作成
スプレッドシートに記載したメールアドレスと名前から、アカウントを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3949968/4d95a5d5-33e6-dc95-9989-567858d49ca7.png)

こちらもスコープがある事に注意。
`https://www.googleapis.com/auth/admin.directory.user`

```javascript:gws_create.gs
function createUsers() {
  // スプレッドシートのIDとシート名を指定
  const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("アカウント登録用");
  var data = sh.getDataRange().getValues();

  // ヘッダー行をスキップ
  data.shift();
  //スプレッドシートに記載されたユーザーデータを取得
  data.forEach(function(row) {
    var fName = row[0];
    var gName = row[1];
    var email = row[2];

    //登録するユーザーを作成
  var user = {
    primaryEmail: email,
    name: {
      familyName: fName,
      givenName: gName
    },
    password: 'password'
  };

    //ユーザー作成
    try {
      var newUser = AdminDirectory.Users.insert(user);
      Logger.log('ユーザーを作成しました: ' + email);
    } catch(error) {
      Logger.log('ユーザーの作成に失敗しました: ' + error.message);
    }
  });
}
```

### アカウントの削除
スプレッドシートに記載したメールアドレスと名前から、アカウントを削除します。

```javascript:gws_get.gs
function deleteUsers() {
  // スプレッドシートのIDとシート名を指定
  const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("アカウント削除用");
  var data = sh.getDataRange().getValues();

  // ヘッダー行をスキップ
  data.shift();

  data.forEach(function(row) {
    var userKey = row[2];

  try {
      var deleteUser = AdminDirectory.Users.remove(userKey);
      Logger.log('ユーザーを削除しました: ' + userKey);
    } catch(error) {
      Logger.log('ユーザーの削除に失敗しました: ' + error.message);
    }
  });
}
```


# その他応用
### カスタム属性の確認
サービスアカウントから、GWSアカウントのカスタム属性を確認してみます。

```javascript:gws_custom_attributes.gs
function getUserCustomAttributes() {
  // スプレッドシートのIDとシート名を指定
  var spreadsheetId = 'SPREADSHEET_ID';
  var sheetName = 'Sheet1';

  // サービスアカウントの認証情報
  var serviceAccount = getServiceAccountClient();

  // スプレッドシートからユーザーのメールアドレスを取得
  var sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);
  var data = sheet.getDataRange().getValues();
  var userEmail = data[0][0]; // A1セルにメールアドレスを記載

  // ユーザーのカスタム属性を取得
  try {
    var user = serviceAccount.users().get({
      userKey: userEmail
    }).execute();
    var customAttributes = user.customSchema;
    Logger.log('カスタム属性: ' + JSON.stringify(customAttributes));
  } catch(error) {
    Logger.log('ユーザーの取得に失敗しました: ' + error.message);
  }
}

// サービスアカウントの認証情報を取得する関数
function getServiceAccountClient() {
  // サービスアカウントのキーファイルのパスを指定
  var keyFile = 'path/to/your/key.json';

  return new GoogleAppsScript.OAuth2.ServiceAccount(
    'https://www.googleapis.com/auth/admin.directory.user',
    keyFile,
    'YOUR_DOMAIN'
  );
}
```

### 動的グループのクエリの管理
動的グループの設定時に作成する、クエリの編集や確認方法です。

```javascript:gws_group_query.gs
function getDynamicGroupQuery() {
  // スプレッドシートのIDとシート名を指定
  var spreadsheetId = 'SPREADSHEET_ID';
  var sheetName = 'Sheet1';

  // サービスアカウントの認証情報
  var serviceAccount = getServiceAccountClient();

  // スプレッドシートから動的グループのIDを取得
  var sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);
  var data = sheet.getDataRange().getValues();
  var groupId = data[0][0]; // A1セルにグループIDを記載

  // 動的グループのクエリを取得
  try {
    var group = serviceAccount.groups().get({
      groupKey: groupId
    }).execute();
    Logger.log('動的グループのクエリ: ' + group.query);
  } catch(error) {
    Logger.log('動的グループの取得に失敗しました: ' + error.message);
  }
}

// サービスアカウントの認証情報を取得する関数
function getServiceAccountClient() {
  // サービスアカウントのキーファイルのパスを指定
  var keyFile = 'sample_key.json';

  return new GoogleAppsScript.OAuth2.ServiceAccount(
    'https://www.googleapis.com/auth/admin.directory.user',
    keyFile,
    'YOUR_DOMAIN'
  );
```

# まとめ
Admin SDK APIを使用することで、スプレッドシート上でのアカウント管理が可能です。
スケジューラー設定から定期実行も可能なので、フォームと組み合わせたりするとフォーム申請からアカウント作成も自動化出来ますね。
また、組織部門やグループの管理、動的グループのクエリ編集も可能ですので、組織管理にも使えます。
アカウント管理はどこでも発生する業務ですが、管理方法の最適化が毎度難しく悩むので、汎用性のある安価で堅牢で簡易な方法が欲しいです。

## 参考
[https://developers.google.com/admin-sdk/reference-overview?hl=ja](https://developers.google.com/admin-sdk/reference-overview?hl=ja)
[https://zenn.dev/acompany/articles/51e1dcc83279ee](https://zenn.dev/acompany/articles/51e1dcc83279ee)
[https://www.wantedly.com/companies/rakus/post_articles/140168](https://www.wantedly.com/companies/rakus/post_articles/140168)