Active Directory のユーザー情報をグループ含めてCSVに出力する


目的

現在のActive Directoryのユーザー情報をPythonで取得しようと思い、
ldap3を利用して、Active Directoryのユーザー情報を取得してみました。

環境

  • Windows 10 x64 1809
  • Python 3.6.5 x64
  • Power Shell 6 x64
  • Visual Studio Code x64
  • Git for Windows x64

ldap3を利用したユーザー情報取得の流れ

PythonでActive Directoryを参照するにはldap3が良さそうです。

> pip install ldap3

まずは、ldap3をインポートして、Serverインスタンスを生成してみます。

from ldap3 import Server, Connection, ALL, NTLM, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, AUTO_BIND_NO_TLS, SUBTREE
from ldap3.core.exceptions import LDAPCursorError
server = Server('サーバー名', port = 389, get_info = ALL)

ポートは389固定、すべての情報を取得としています。

続いて、Active Directoryに接続してみます。

conn = Connection(server, user = 'ドメイン\\ユーザー',
                          'パスワード',
                          authentication = NTLM,
                          auto_bind = True)

authenticationをNTLMとしています。

そして、ユーザー情報を問い合わせてみます。

conn.search('DC=hoge,DC=loacl',
            '(&(|(objectclass=user)(objectclass=person)(objectclass=inetOrgPerson)(objectclass=organizationalPerson))(!(objectclass=computer)))',
            attributes = [ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES])

問い合わせの対象をユーザー情報に限定しています。(コンピューターを除外)

あとは、取得した結果を利用します。

for entry in sorted(conn.entries):
    print(entry.sAMAccountName)

存在しない情報を参照するとLDAPCursorErrorが発生するので、適宜try/exceptを行います。

ldap3を利用したユーザー情報取得のコード

> pip install ldap3
> pip install python-dotenv
import sys
import csv
import settings
from ldap3 import Server, Connection, ALL, NTLM, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, AUTO_BIND_NO_TLS, SUBTREE
from ldap3.core.exceptions import LDAPCursorError

class ADInfo:
    # コンストラクタ
    def __init__(self, server_name, domain_name, user_name, password, search_dc):
        self.__server_name = server_name
        self.__domain_name = domain_name
        self.__user_name = user_name
        self.__password = password
        self.__search_dc = search_dc

    # 接続
    def connect(self):
        self.__server = Server(self.__server_name, port = 389, get_info = ALL)
        self.__conn = Connection(self.__server,
                                 user = f'{self.__domain_name}\\{self.__user_name}',
                                 password = self.__password,
                                 authentication = NTLM,
                                 auto_bind = True)

    # 接続ユーザーを確認
    def who_am_i(self):
        return self.__conn.extend.standard.who_am_i()

    # ユーザー情報を取得
    def search_users(self):
        self.__conn.search(self.__search_dc,
                           '(&(|(objectclass=user)(objectclass=person)(objectclass=inetOrgPerson)(objectclass=organizationalPerson))(!(objectclass=computer)))',
                           attributes = [ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES])
        return self.__conn.entries

    # CSVファイルに出力
    def output_users(self, entries, csv_filepath):
        with open(csv_filepath, 'w', encoding="cp932", errors="ignore", newline='') as f:
            writer = csv.writer(f)
            writer.writerow(['id', '氏名', '説明', '所属グループ'])
            for entry in sorted(entries):
                try:
                    desc = entry.description
                except LDAPCursorError:
                    desc = ""
                try:
                    memberOf = entry.memberOf
                except LDAPCursorError:
                    memberOf = [""]
                for group in memberOf:
                    group_cnname = group.split(",")[0].lstrip("CN=")
                    writer.writerow([entry.sAMAccountName, entry.name, desc, group_cnname])
                    # print([entry.sAMAccountName, entry.name, desc, group_cnname])

RETURN_SUCCESS = 0
RETURN_FAILURE = -1

def main():
    print("===================================================================")
    print("Active Directory のユーザー情報を取得してCSVファイルを出力します。")
    print("ユーザーごとに所属しているグループも取得します。")
    print("===================================================================")

    # 引数のチェック
    argvs = sys.argv
    if len(argvs) != 2 or not argvs[1]:
        print("CSVファイルのパスを指定してください。")
        return RETURN_FAILURE

    # CSVファイルパスの取得
    csv_filepath = argvs[1].strip()

    try:
        # AD情報
        adinfo = ADInfo(settings.SERVER_NAME,
                        settings.DOMAIN_NAME,
                        settings.USER_NAME,
                        settings.PASSWORD,
                        settings.SEARCH_DC)
        adinfo.connect()
        print(adinfo.who_am_i())

        # CSV出力
        entries = adinfo.search_users()
        adinfo.output_users(entries, csv_filepath)
    except Exception as e:
        print(e)
        return RETURN_FAILURE

    return RETURN_SUCCESS

if __name__ == "__main__":
    main()

設定情報はpython-dotenvを利用しています。

import os
from os.path import join, dirname
from dotenv import load_dotenv

dotenv_path = join(dirname(__file__), '.env')
load_dotenv(dotenv_path)

SERVER_NAME = os.environ.get("SERVER_NAME")
DOMAIN_NAME = os.environ.get("DOMAIN_NAME")
USER_NAME = os.environ.get("USER_NAME")
PASSWORD = os.environ.get("PASSWORD")
SEARCH_DC = os.environ.get("SEARCH_DC")

設定ファイル(.env)はこんな感じ

SERVER_NAME = 'IPorSERVERNAME'
DOMAIN_NAME = 'DOMAINNAME'
USER_NAME = 'USERNAME'
PASSWORD = 'PASSWORD'
SEARCH_DC = 'DC=xxxx,DC=xxxx'

まとめ

Pythonでユーザー情報を取得することができました。
いろいろな情報が取得できそうなので、ユーザーやグループ以外の情報も活用できそうです。

取得対象の件数が多い場合は、extend.standard.paged_searchメソッドでページ単位の検索をしたほうがよいようです。