以前書いた Active Directory データのプロパティ出力(VB、C#、スライド) の COM 対応版です。
なんで "大きい整数" 対応版にしてないかというと、"大きい整数" で表される IADsLargeInteger のほかにも COM(ADSI オブジェクト)があるからです。
それはセキュリティ記述子です。これの属性は nTSecurityDescriptor で IADsSecurityDescriptor として扱え、値をこのインターフェイスにキャストできます。
nTSecurityDescriptor 属性は通常のプロパティ(DirectoryEntry.Properties)で取得できます。オプションのプロパティでは取得できません。
以前からの変更点
・セキュリティ記述子を見やすい形で出力する
・"大きい整数" を数値や日付で出力する
・構造化例外処理を使わないで値がないかを確認する
・COM じゃない値(バイト配列や日付)も正しく出力する
説明は別途ということで まずはコードを。
Public Shared Sub OutputProperties(entry AsDirectoryEntry, filePath As String)
Dim props = entry.Properties.PropertyNames.Cast(Of String)().OrderBy(Function(s) s).ToList() 'プロパティ名のリスト
Using writer As NewStreamWriter(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 IADsSecurityDescriptorThen 'セキュリティ記述子の時
Dim sd = DirectCast(val, IADsSecurityDescriptor)
Dim aceGroups = DirectCast(sd.DiscretionaryAcl, IADsAccessControlList).Cast(OfIADsAccessControlEntry)().OrderBy(
Function(ace) 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, 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 IADsLargeIntegerThen '大きい整数の時
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(pname, pval) '値を取得
writer.WriteLine("{0}:{1}", pname, value)
Next
End If
Next
End Using
End Sub
Public Shared Sub OutputOptionalProperties(entry AsDirectoryEntry, filePath As String)
Dim schema = DirectCast(entry.SchemaEntry.NativeObject, IADsClass) 'スキーマ オブジェクト
Dim props = DirectCast(schema.OptionalProperties, Object()) 'オプションのプロパティ
Using writer As NewStreamWriter(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 IADsLargeIntegerThen '大きい整数の時
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(pname, 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 NewSecurityIdentifier(value, 0).ToString()
ElseIf name.Equals("objectGUID") Then
Return NewGuid(value).ToString()
Else
ReturnBitConverter.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(name As String, 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 AsType) As String
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 = 0Then '設定されている値がない時
Return value.ToString()
End If
'AceType(ADS_ACETYPE_ENUM)の時にビットマスクがかぶってしまうので、列挙値が小さい方を削除
Dim total = values.Select(Function(e) Convert.ToInt32([Enum].Parse(enumType, e))).Sum(Function(e) e)
If value <> total Then 'ビットマスクがかぶっている時
values.Remove([Enum].ToObject(enumType, total - value).ToString())
End If
Return String.Format("{0}({1})", value, String.Join(" | ", values))
End Function