Dynamics CRM 2011 : 監査の記録を SDK で取得する

みなさん、こんにちは。

今回も管理者と開発者ともに関連する内容として、SDK を利用した
監査ログの出力を紹介します。

概要

Microsoft Dynamics CRM 2011 では、レコード操作の監査機能が
あります。これはレコードの作成、更新、削除を監査できる機能で
その結果は画面より確認が可能です。

しかしエクセルにエクスポートやレポートに出力といった機能には
対応しないことから、データを外部に出力したい場合には SDK を
利用した開発が必要となります。

今回は SDK で提供されるいくつかの機能を組み合わせて、監査
ログの出力を行ってみます。

監査ログ取得のサンプル

監査ログ本体の取得

今回は QueryExpression を利用して監査ログの一覧を取得して
みます。またログの種類を指定し、作成、更新、削除のログのみ
取得してみます。この際ヘルパーコードの optionsets.cs を利用
すれば、容易に指定が可能です。

// QueryExpression を利用して監査を取得
QueryExpression qe = new QueryExpression()
{
    // エンティティ名として Audit を指定
    EntityName = Audit.EntityLogicalName,
    // 条件として Action を指定
    Criteria =
    {
        // 特定のイベントのみ取得
        Conditions = { new ConditionExpression("action", ConditionOperator.In, new int[]{
            // optionset.cs を利用したオプションセットの指定
            (int)AuditAction.Update,
            (int)AuditAction.Create,
            (int)AuditAction.Delete})}
    },
    Orders =
    {new OrderExpression("createdon", OrderType.Descending)}                   
};

// RetrieveMultipleRequest でデータを取得
RetrieveMultipleRequest retrieveMultipleRequest = new RetrieveMultipleRequest()
{
    Query = qe
};

EntityCollection retrieveMultipleResponse =
((RetrieveMultipleResponse)_serviceProxy.Execute(retrieveMultipleRequest)).EntityCollection;

AuditDetail の取得

監査ログ本体を取得したら、ログの詳細を取得します。

// 取得した監査より AuditDetail を取得
foreach (Audit au in retrieveMultipleResponse.Entities)
{
    RetrieveAuditDetailsRequest request = new RetrieveAuditDetailsRequest();
    request.AuditId = (Guid)au.AuditId;

    RetrieveAuditDetailsResponse response = (RetrieveAuditDetailsResponse)_serviceProxy.Execute(request);
    // 取得した response に含まれる AuditDetails を 表示
    DisplayAuditDetails(response.AuditDetail);

AuditDetail の表示

最後に取得した監査ログの詳細を画面に出力します。
監査ログには種類があるため、フィールドの監査をしている点や
以前の情報または新しい情報を持っているか確認します。また
フィールドによっては文字列以外の型を持っているため、適切に
処理する必要があります。

private static void DisplayAuditDetails(AuditDetail detail)
{
    // 監査ログの詳細より監査ログ本体を再度取得。 
    Audit record = (Audit)detail.AuditRecord;

    // 監査ログ本体のプロパティより情報を出力
    Console.WriteLine("\n監査レコード作成日時: {0}", record.CreatedOn.Value.ToLocalTime());
    Console.WriteLine("エンティティ: {0}, イベント: {1}, 操作: {2}",
        record.ObjectId.LogicalName, record.FormattedValues["action"],
        record.FormattedValues["operation"]);

    // 監査ログの詳細より種類を取得
    var detailType = detail.GetType();
    // フィールドの監査だった場合は情報を出力
    if (detailType == typeof(AttributeAuditDetail))
    {
        // 監査されたフィールドの情報を取得
        var attributeDetail = (AttributeAuditDetail)detail;

        // 新しい値がある場合
        if (attributeDetail.NewValue != null)
        {
            foreach (KeyValuePair<String, object> attribute in attributeDetail.NewValue.Attributes)
            {
                String oldValue = "(値なし)", newValue = "(値なし)";

                // 以前の値もあれば表示
                if (attributeDetail.OldValue.Contains(attribute.Key))
                {
                    // 以前の値を取得
                    oldValue = attributeDetail.OldValue[attribute.Key].ToString();
                            
                    // 文字列型でない場合、相当する CRM の型に変更して値を取得
                    if (oldValue == "Microsoft.Xrm.Sdk.Money")
                        oldValue = ((Microsoft.Xrm.Sdk.Money)attributeDetail.OldValue[attribute.Key]).Value.ToString();
                    else if (oldValue == "Microsoft.Xrm.Sdk.OptionSetValue")
                        oldValue = ((Microsoft.Xrm.Sdk.OptionSetValue)attributeDetail.OldValue[attribute.Key]).Value.ToString();
                    // 参照型の場合、参照先のレコードがすでにない可能性があるため、null チェック必須
                    else if (oldValue == "Microsoft.Xrm.Sdk.EntityReference")
                        oldValue = ((Microsoft.Xrm.Sdk.EntityReference)attributeDetail.OldValue[attribute.Key]).Name == null ?
                            "使用できないレコード" : ((Microsoft.Xrm.Sdk.EntityReference)attributeDetail.OldValue[attribute.Key]).Name.ToString();
                }

                // 新しい値を取得
                newValue = attributeDetail.NewValue[attribute.Key].ToString();

                // 文字列型でない場合、相当する CRM の型に変更して値を取得
                if (newValue == "Microsoft.Xrm.Sdk.Money")
                    newValue = ((Microsoft.Xrm.Sdk.Money)attributeDetail.NewValue[attribute.Key]).Value.ToString();
                else if (newValue == "Microsoft.Xrm.Sdk.OptionSetValue")
                    newValue = ((Microsoft.Xrm.Sdk.OptionSetValue)attributeDetail.NewValue[attribute.Key]).Value.ToString();
                // 参照型の場合、参照先のレコードがすでにない可能性があるため、null チェック必須
                else if (newValue == "Microsoft.Xrm.Sdk.EntityReference")
                    newValue = ((Microsoft.Xrm.Sdk.EntityReference)attributeDetail.NewValue[attribute.Key]).Name == null ?
                        "使用できないレコード" : ((Microsoft.Xrm.Sdk.EntityReference)attributeDetail.NewValue[attribute.Key]).Name.ToString();

                Console.WriteLine("フィールド: {0}, 以前の値: {1}, 新しい値: {2}",
                    attribute.Key, oldValue, newValue);
            }
        }

        if (attributeDetail.OldValue != null)
        foreach (KeyValuePair<String, object> attribute in attributeDetail.OldValue.Attributes)
        {
            // 新しい値があれば、すでに古い値の処理も終わっているため処理をスキップ
            if (attributeDetail.NewValue == null)
            {
                // 新しい値がないことが分かってるため、値なしを設定
                String newValue = "(値なし)";
                
                // 以前の値を取得                               
                String oldValue = attributeDetail.OldValue[attribute.Key].ToString();
                // 文字列型でない場合、相当する CRM の型に変更して値を取得
                if (oldValue == "Microsoft.Xrm.Sdk.Money")
                    oldValue = ((Microsoft.Xrm.Sdk.Money)attributeDetail.OldValue[attribute.Key]).Value.ToString();
                else if (oldValue == "Microsoft.Xrm.Sdk.OptionSetValue")
                    oldValue = ((Microsoft.Xrm.Sdk.OptionSetValue)attributeDetail.OldValue[attribute.Key]).Value.ToString();
                // 参照型の場合、参照先のレコードがすでにない可能性があるため、null チェック必須
                else if (oldValue == "Microsoft.Xrm.Sdk.EntityReference")
                    oldValue = ((Microsoft.Xrm.Sdk.EntityReference)attributeDetail.OldValue[attribute.Key]).Name == null ?
                        "使用できないレコード" : ((Microsoft.Xrm.Sdk.EntityReference)attributeDetail.OldValue[attribute.Key]).Name.ToString();

                Console.WriteLine("フィールド: {0}, 以前の値: {1}, 新しい値: {2}",
                    attribute.Key, oldValue, newValue);
            }
        }
    }
    Console.WriteLine();

その他の考慮点

今回はレコードの作成、更新、削除イベントのみを取得し、結果を表示
していますが、他イベントを取得した場合にどのように出力するか、また
今回処理していない型が存在する場合の対処なども必要になります。

オプションセット、フィールド名、エンティティ名は値が取得できるだけ
なので、ラベルの表示が必要な場合は、メタデータに対するクエリが
必要になります。

まとめ

監査記録は様々な情報を保持していますので、出力するとしても
内容によって処置が異なる点検討が必要となります。今回紹介
したサンプルコードはすべて SDK にある情報のみで作成しています
ので、メソッドの詳細やクラスの詳細が不明な場合は、是非最新の
SDK をご確認ください。

‐ Dynamics CRM サポート 中村 憲一郎