以前書いた Active Directory データのプロパティ出力(VB、C#、スライド) の COM 対応版です。
※2013/12/10 Trustee はユーザやグループの名前部分のみ出力、AceType は値とその列挙体文字列を出力するよう修正しました。
※2013/12/11 GetValue メソッドの name パラメータを削除しました。
なんで "大きい整数" 対応版にしてないかというと、"大きい整数" で表される IADsLargeInteger のほかにも COM(ADSI オブジェクト)があるからです。
それはセキュリティ記述子です。これの属性は nTSecurityDescriptor で IADsSecurityDescriptor として扱え、値をこのインターフェイスにキャストできます。
nTSecurityDescriptor 属性は通常のプロパティ(DirectoryEntry.Properties)で取得できます。オプションのプロパティでは取得できません。
以前からの変更点
・セキュリティ記述子を見やすい形で出力する
・"大きい整数" を数値や日付で出力する
・構造化例外処理を使わないで値がないかを確認する
・COM じゃない値(バイト配列や日付)も正しく出力する
説明は別途ということで まずはコードを。
Public Shared Sub OutputProperties(entry As DirectoryEntry, filePath As String)
Dim props = entry.Properties.PropertyNames.Cast(Of String)().OrderBy(Function(s) s).ToList() 'プロパティ名のリスト
Using writer As New StreamWriter(filePath, False, Encoding.UTF8)
For Each pname In props 'プロパティ数分
Dim val = entry.Properties.Item(pname).Value
If TypeOf val Is Byte() Then 'バイト配列の時
Dim pstr = GetByteValue(pname, DirectCast(val, Byte())) 'バイト値を取得
writer.WriteLine("{0}:{1}", pname, pstr)
ElseIf TypeOf val Is IADsSecurityDescriptor Then 'セキュリティ記述子の時
Dim sd = DirectCast(val, IADsSecurityDescriptor)
Dim aceGroups = DirectCast(sd.DiscretionaryAcl, IADsAccessControlList).Cast(Of IADsAccessControlEntry)().OrderBy(
Function(ace) Path.GetFileName(ace.Trustee)).ThenBy(Function(ace) ace.AccessMask).ThenBy(
Function(ace) ace.AceFlags).ThenBy(Function(ace) ace.AceType).ThenBy(Function(ace) ace.Flags).GroupBy(
Function(ace) String.Format("{0}|{1}|{2}|{3}|{4}",
ace.Trustee, ace.AccessMask, ace.AceFlags, ace.AceType, ace.Flags)).ToList() 'プロパティ値でグループ化したACE
Dim ctr = 0
writer.WriteLine(pname)
For Each aceGroup In aceGroups 'ACE数分
Dim ace = aceGroup.First()
ctr += 1
writer.WriteLine(" {0:D2}. Trustee :{1}", ctr, Path.GetFileName(ace.Trustee))
writer.WriteLine(" {0:D2}. AccessMask:{1}", ctr, ToEnumValueText(ace.AccessMask, GetType(ADS_RIGHTS_ENUM)))
writer.WriteLine(" {0:D2}. AceFlags :{1}", ctr, ToEnumValueText(ace.AceFlags, GetType(ADS_ACEFLAG_ENUM)))
writer.WriteLine(" {0:D2}. AceType :{1}", ctr, ToEnumValueText(ace.AceType, GetType(ADS_ACETYPE_ENUM)))
writer.WriteLine(" {0:D2}. Flags :{1}", ctr, ToEnumValueText(ace.Flags, GetType(ADS_FLAGTYPE_ENUM)))
Next
ElseIf TypeOf val Is IADsLargeInteger Then '大きい整数の時
Dim li = GetLargeIntegerValue(pname, DirectCast(val, IADsLargeInteger)) '大きい整数値を取得
writer.WriteLine("{0}:{1}", pname, li)
Else 'それ以外の時
For Each pval In entry.Properties.Item(pname) '値数分
Dim value = GetValue(pval) '値を取得
writer.WriteLine("{0}:{1}", pname, value)
Next
End If
Next
End Using
End Sub
Public Shared Sub OutputOptionalProperties(entry As DirectoryEntry, filePath As String)
Dim schema = DirectCast(entry.SchemaEntry.NativeObject, IADsClass) 'スキーマ オブジェクト
Dim props = DirectCast(schema.OptionalProperties, Object()) 'オプションのプロパティ
Using writer As New StreamWriter(filePath, False, Encoding.UTF8)
entry.Invoke("GetInfoEx", props, 0) 'プロパティをディレクトリ ストアから読込
For Each pname As String In props 'オプションのプロパティ数分
Dim pvcol = entry.Properties.Item(pname) 'PropertyValueCollection
If pvcol.Value Is Nothing Then '値がない時
writer.WriteLine("{0}:<未設定>", pname)
Continue For
End If
If TypeOf pvcol.Value Is Byte() Then 'バイト配列の時
Dim bstr = GetByteValue(pname, DirectCast(pvcol.Value, Byte())) 'バイト値を取得
writer.WriteLine("{0}:{1}", pname, bstr)
ElseIf TypeOf pvcol.Value Is IADsLargeInteger Then '大きい整数の時
Dim li = GetLargeIntegerValue(pname, DirectCast(pvcol.Value, IADsLargeInteger)) '大きい整数値を取得
writer.WriteLine("{0}:{1}", pname, li)
Else 'それ以外の時
For Each pval In pvcol '値数分
If TypeOf pval Is Byte() Then 'バイト配列の時
Dim pstr = GetByteValue(pname, DirectCast(pval, Byte())) 'バイト値を取得
writer.WriteLine("{0}:{1}", pname, pstr)
Else 'バイト配列以外の時
Dim value = GetValue(pval) '値を取得
writer.WriteLine("{0}:{1}", pname, value)
End If
Next
End If
Next
End Using
End Sub
'バイト値を取得
Private Shared Function GetByteValue(name As String, value As Byte()) As String
If name.Equals("objectSid") Then
Return New SecurityIdentifier(value, 0).ToString()
ElseIf name.Equals("objectGUID") Then
Return New Guid(value).ToString()
Else
Return BitConverter.ToString(value)
End If
End Function
'大きい整数値を取得
Private Shared Function GetLargeIntegerValue(name As String, value As IADsLargeInteger) As Object
Dim lval = Convert.ToInt64(value.HighPart.ToString("x8") & value.LowPart.ToString("x8"), 16)
If name.Equals("lockoutTime") Then 'ロックアウトしたことがある時
If lval > 0 Then 'ロックアウト中又はロックアウト期間が過ぎただけの時
Return String.Format("{0}({1})", lval, DateTime.FromFileTime(lval)) '判り易いよう日付も付ける
End If
Return lval '明示的にロック解除又はロックアウト期間が過ぎてログオン成功しているので値は0
End If
If IsInteger(name, lval) Then '整数の時
Return lval
End If
If (lval = 0) OrElse (lval = Int64.MaxValue) Then
Return "(なし)"
End If
Return DateTime.FromFileTime(lval) 'eq. #1/1/1601#.AddTicks(lval).ToLocalTime()
End Function
'整数かどうか
Private Shared Function IsInteger(name As String, value As Long) As Boolean
If name.Equals("maxStorage") Then
Return True
End If
If name.StartsWith("msDS-") Then
Return False
End If
If name.StartsWith("uSN") Then
Return True
End If
Return False
End Function
'値を取得
Private Shared Function GetValue(value As Object) As Object
If (TypeOf value Is Date) = False Then '日付ではない時
Return value
End If
Dim d = Convert.ToDateTime(value)
Return If(d.Date.Equals(#1/1/1601#), d, d.ToLocalTime())
End Function
'列挙体のプロパティ値をテキスト化
Private Shared Function ToEnumValueText(value As Integer, enumType As Type) As String
If enumType Is
GetType(ADS_ACETYPE_ENUM) Then 'AceTypeの時
Return String.Format("{0}({1})", value, [Enum].ToObject(enumType, value))
End If
Dim selector = Function(e As Integer) [Enum].ToObject(enumType, e).ToString()
Dim values = [Enum].GetValues(enumType).Cast(Of Integer)().Where(
Function(e) (value And e) = e).OrderBy(selector).Select(selector).ToList() '設定されている値の列挙体文字列
If values.Count = 0 Then '設定されている値がない時
Return value.ToString()
End If
Return String.Format("{0}({1})", value, String.Join(" | ", values))
End Function