セキュリティ記述子(SecurityDescriptor)のADSIに対応する.NETのクラスを調べたので実装してみました。
Active Directoryデータのプロパティ出力のCOM対応版(VB)と比較しながら見ていただければと思います。
コードはサンプルアプリに組み込む前提で書いてます。
追加で System.Security.AccessControl 名前空間をインポートしてます。
説明は別途ということでまずはコードを。
最初は呼出し側。出力部分はメソッド化しました。
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 'プロパティ数分
If pname.Equals("nTSecurityDescriptor") Then 'セキュリティ記述子の時
writer.WriteLine(pname)
OutputSecurityDescriptor(entry.ObjectSecurity, writer) 'セキュリティ記述子を出力
Continue For
End If
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 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
続いて出力処理側。DirectoryEntry.ObjectSecurityプロパティのとこに少し書きましたが、Trustee の名前部分(ユーザ名やグループ名)を取得する必要があるので、別途メソッド化しました。
'セキュリティ記述子を出力
Private Shared Sub OutputSecurityDescriptor(security As ActiveDirectorySecurity, writer As StreamWriter)
Dim sd As New CommonSecurityDescriptor(True, True, security.GetSecurityDescriptorBinaryForm(), 0)
Dim aceList = sd.DiscretionaryAcl.Cast(Of QualifiedAce)().ToList()
Dim sidList = aceList.GroupBy(Function(ace) ace.SecurityIdentifier).Select(
Function(group) group.First().SecurityIdentifier.Value).ToList()
Dim sidNameDic = CreateSidNameDictionary(sidList) 'SID/アカウント名のコレクションを作成
Dim aceGroups = aceList.OrderBy(Function(ace) sidNameDic.Item(ace.SecurityIdentifier.Value)).ThenBy(
Function(ace) ace.AccessMask).ThenBy(Function(ace) ace.AceFlags).ThenBy(Function(ace) ace.AceType).ThenBy(
Function(ace) If(TypeOf ace Is ObjectAce, DirectCast(ace, ObjectAce).ObjectAceFlags, 0)).GroupBy(
Function(ace) String.Format("{0}|{1}|{2}|{3}|{4}",
sidNameDic.Item(ace.SecurityIdentifier.Value), ace.AccessMask, ace.AceFlags, ace.AceType,
If(TypeOf ace Is ObjectAce, DirectCast(ace, ObjectAce).ObjectAceFlags, 0))).ToList() 'プロパティ値でグループ化したACE
Dim ctr = 0
For Each aceGroup In aceGroups 'ACE数分
Dim ace = aceGroup.First()
ctr += 1
writer.WriteLine(" {0:D2}. Trustee :{1}", ctr, sidNameDic.Item(ace.SecurityIdentifier.Value))
writer.WriteLine(" {0:D2}. AccessMask:{1}", ctr, ToEnumValueText(ace.AccessMask, GetType(ActiveDirectoryRights)))
writer.WriteLine(" {0:D2}. AceFlags :{1}", ctr, ToEnumValueText(ace.AceFlags, GetType(AceFlags)))
writer.WriteLine(" {0:D2}. AceType :{1}", ctr, ToEnumValueText(ace.AceType, GetType(AceType)))
If TypeOf ace Is ObjectAce Then 'ディレクトリ オブジェクトに関連付けられたACEの時
writer.WriteLine(" {0:D2}. Flags :{1}", ctr,
ToEnumValueText(DirectCast(ace, ObjectAce).ObjectAceFlags, GetType(ObjectAceFlags)))
Else 'CommonAce(ACE)の時
writer.WriteLine(" {0:D2}. Flags :0", ctr)
End If
Next
End Sub
'SID/アカウント名のコレクションを作成
Private Shared Sub CreateSidNameDictionary(sidList As List(Of String)) As Dictionary(Of String, String)
Dim sidNameDic As New Dictionary(Of String, String)(sidList.Count)
Using root = GetRootEntry() 'ルートのDirectoryEntryを取得
Using searcher As New DirectorySearcher(root)
For Each sid In sidList 'SID数分
searcher.Filter = String.Format("(objectSid={0})", sid)
Dim res = searcher.FindOne() '検索
If res Is Nothing Then '見つからなかった時(SYSTEM、SELF、Everyoneなど)
Dim acc = New SecurityIdentifier(sid).Translate(GetType(NTAccount)) 'アカウントに変換
sidNameDic.Add(sid, Path.GetFileName(acc.Value))
Else '見つかった時
Using entry = res.GetDirectoryEntry()
Dim objectType = DirectCast([Enum].Parse(GetType(CategoryType), entry.SchemaClassName, True), CategoryType)
If objectType = CategoryType.ForeignSecurityPrincipal Then '外部のセキュリティプリンシパルの時
Dim acc = New SecurityIdentifier(sid).Translate(GetType(NTAccount)) 'アカウントに変換
sidNameDic.Add(sid, Path.GetFileName(acc.Value))
Else '外部のセキュリティプリンシパル以外の時
sidNameDic.Add(sid, entry.Properties.Item("cn").Value.ToString())
End If
End Using
End If
Next
End Using
End Using
Return sidNameDic
End Function
最後に既存のメソッド。内部実装を変更しました。
'列挙体のプロパティ値をテキスト化
Private Shared Function ToEnumValueText(value As Integer, enumType As Type) As String
If enumType Is GetType(AceType) 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 Object)().Select(Function(e) Convert.ToInt32(e)).Where(
Function(e) (value And e) = e).OrderBy(selector).Select(selector).ToList() '設定されている値の列挙体文字列
values.Remove("None")
If values.Count = 0 Then '設定されている値がない時
Return value.ToString()
End If
Return String.Format("{0}({1})", value, String.Join(" | ", values))
End Function