Search code examples
jsonvb.netjson.net

How to make my VB.NET code dynamic instead of static?


The following code gives me the error system.argumentexception an element with the same key already exists. When I use in the the Friend Sub Test the following line instead: 'Dim str_rootdirectory As String = Directory.GetCurrentDirectory() ' "C:\TEMP" it works. Whats the difference?

My VB.NET code:

Public Class Form1
    Public Sub recur_getdirectories(ByVal di As DirectoryInfo)
        For Each directory As DirectoryInfo In di.GetDirectories()
            'get each directory and call the module main to get the security info and write to json
            Call Module1.Main(directory.FullName)
            recur_getdirectories(directory)
        Next
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim rootDirectory As String = TextBox1.Text.Trim()
        Dim di As New DirectoryInfo(rootDirectory)

        'get directories recursively and work with each of them
        recur_getdirectories(di)
    End Sub
End Class

Public Module RecursiveEnumerableExtensions
    Iterator Function Traverse(Of T)(ByVal root As T, ByVal children As Func(Of T, IEnumerable(Of T)), ByVal Optional includeSelf As Boolean = True) As IEnumerable(Of T)
        If includeSelf Then Yield root
        Dim stack = New Stack(Of IEnumerator(Of T))()

        Try
            stack.Push(children(root).GetEnumerator())
            While stack.Count <> 0
                Dim enumerator = stack.Peek()
                If Not enumerator.MoveNext() Then
                    stack.Pop()
                    enumerator.Dispose()
                Else
                    Yield enumerator.Current
                    stack.Push(children(enumerator.Current).GetEnumerator())
                End If
            End While
        Finally
            For Each enumerator In stack
                enumerator.Dispose()
            Next
        End Try
    End Function
End Module

Public Module TestClass
    Function GetFileSystemAccessRule(d As DirectoryInfo) As IEnumerable(Of FileSystemAccessRule)
        Dim ds As DirectorySecurity = d.GetAccessControl()
        Dim arrRules As AuthorizationRuleCollection = ds.GetAccessRules(True, True, GetType(Security.Principal.NTAccount))

        For Each authorizationRule As FileSystemAccessRule In arrRules
            Dim strAclIdentityReference As String = authorizationRule.IdentityReference.ToString()
            Dim strInheritanceFlags As String = authorizationRule.InheritanceFlags.ToString()
            Dim strAccessControlType As String = authorizationRule.AccessControlType.ToString()
            Dim strFileSystemRights As String = authorizationRule.FileSystemRights.ToString()
            Dim strIsInherited As String = authorizationRule.IsInherited.ToString()
        Next

        ' This function should return the following values, because they should be mentoined in the JSON:
        ' IdentityReference = strAclIdentityReference
        ' InheritanceFlags = strInheritanceFlags
        ' AccessControlType = strAccessControlType
        ' FileSystemRights = strFileSystemRights
        ' IsInherited = strIsInherited

        Return ds.GetAccessRules(True, True, GetType(System.Security.Principal.NTAccount)).Cast(Of FileSystemAccessRule)()
    End Function

    Friend Sub Test(ByVal curDirectory As String)
        'Dim str_rootdirectory As String = Directory.GetCurrentDirectory() ' "C:\TEMP"
        Dim str_rootdirectory As String = curDirectory

        Dim di As DirectoryInfo = New DirectoryInfo(str_rootdirectory)

        Dim directoryQuery = RecursiveEnumerableExtensions.Traverse(di, Function(d) d.GetDirectories())

        Dim list = directoryQuery.Select(
            Function(d) New With {
                .directory = d.FullName,
                .permissions = {
                    GetFileSystemAccessRule(d).ToDictionary(Function(a) a.IdentityReference.ToString(), Function(a) a.FileSystemRights.ToString())
                }
            }
        )
        Dim json = JsonConvert.SerializeObject(list, Formatting.Indented)
        File.WriteAllText("ABCD.json", json)
    End Sub
End Module

Public Module Module1
    Public Sub Main(ByVal curDirectory As String)
        Console.WriteLine("Environment version: " & Environment.Version.ToString())
        Console.WriteLine("Json.NET version: " & GetType(JsonSerializer).Assembly.FullName)
        Console.WriteLine("")
        Try
            TestClass.Test(curDirectory)

        Catch ex As Exception
            Console.WriteLine("Unhandled exception: ")
            Console.WriteLine(ex)
            Throw
        End Try
    End Sub
End Module

My example folder structure:

Folder: "C:\Temp"

Permissions: SecurityGroup-A has Fullcontrol, SecurityGroup-B has Modify permission

Folder: "C:\Temp\Folder_A"

Permissions: SecurityGroup-C has Fullcontrol

But this is only an example of two folders. In real, it will run over several hundered folders with sub-folders. Accordingly the JSON will extend.

My json output expectation:

[{
        "directory": "C:\\TEMP",
        "permissions": [{
                "IdentityReference": "CONTOSO\\SecurityGroup-A",
                "AccessControlType": "Allow",
                "FileSystemRights": "FullControl",
                "IsInherited": "TRUE"
            }, {
                "IdentityReference": "CONTOSO\\SecurityGroup-B",
                "AccessControlType": "Allow",
                "FileSystemRights": "Modify",
                "IsInherited": "False"
            }
        ]
    }, {
        "directory": "C:\\TEMP\\Folder_A",
        "permissions": [{
                "IdentityReference": "CONTOSO\\SecurityGroup-C",
                "AccessControlType": "Allow",
                "FileSystemRights": "Full Control",
                "IsInherited": "False"
            }
        ]
    }
]

Solution

  • Your current JSON uses static property names for the [*].permissions[*] objects so there is no need to try to convert a list of them into a dictionary with variable key names via ToDictionary():

    ' This is not needed
    .permissions = {
        GetFileSystemAccessRule(d).ToDictionary(Function(a) a.IdentityReference.ToString(), Function(a) a.FileSystemRights.ToString())
    }
    

    Instead, convert each FileSystemAccessRule into some appropriate DTO for serialization. An anonymous type object works nicely for this purpose:

    Public Module DirectoryExtensions
        Function GetFileSystemAccessRules(d As DirectoryInfo) As IEnumerable(Of FileSystemAccessRule)
            Dim ds As DirectorySecurity = d.GetAccessControl()
            Dim arrRules As AuthorizationRuleCollection = ds.GetAccessRules(True, True, GetType(Security.Principal.NTAccount))
            Return arrRules.Cast(Of FileSystemAccessRule)()
        End Function
        
        Public Function SerializeFileAccessRules(ByVal curDirectory As String, Optional ByVal formatting As Formatting = Formatting.Indented)
            Dim di As DirectoryInfo = New DirectoryInfo(curDirectory)
    
            Dim directoryQuery = RecursiveEnumerableExtensions.Traverse(di, Function(d) d.GetDirectories())
    
            Dim list = directoryQuery.Select(
                Function(d) New With {
                    .directory = d.FullName,
                    .permissions = GetFileSystemAccessRules(d).Select(
                        Function(a) New With { 
                            .IdentityReference = a.IdentityReference.ToString(),
                            .AccessControlType = a.AccessControlType.ToString(),
                            .FileSystemRights = a.FileSystemRights.ToString(),
                            .IsInherited = a.IsInherited.ToString()
                        }
                    )
                }
            )
            Return JsonConvert.SerializeObject(list, formatting)    
        End Function
    End Module
    
    Public Module RecursiveEnumerableExtensions
        ' Translated to vb.net from this answer https://stackoverflow.com/a/60997251/3744182
        ' To https://stackoverflow.com/questions/60994574/how-to-extract-all-values-for-all-jsonproperty-objects-with-a-specified-name-fro
        ' which was rewritten from the answer by Eric Lippert https://stackoverflow.com/users/88656/eric-lippert
        ' to "Efficient graph traversal with LINQ - eliminating recursion" https://stackoverflow.com/questions/10253161/efficient-graph-traversal-with-linq-eliminating-recursion
        Iterator Function Traverse(Of T)(ByVal root As T, ByVal children As Func(Of T, IEnumerable(Of T)), ByVal Optional includeSelf As Boolean = True) As IEnumerable(Of T)
            If includeSelf Then Yield root
            Dim stack = New Stack(Of IEnumerator(Of T))()
    
            Try
                stack.Push(children(root).GetEnumerator())
                While stack.Count <> 0
                    Dim enumerator = stack.Peek()
                    If Not enumerator.MoveNext() Then
                        stack.Pop()
                        enumerator.Dispose()
                    Else
                        Yield enumerator.Current
                        stack.Push(children(enumerator.Current).GetEnumerator())
                    End If
                End While
            Finally
                For Each enumerator In stack
                    enumerator.Dispose()
                Next
            End Try
        End Function
    End Module
    

    Demo fiddle here (which unfortunately does not work on https://dotnetfiddle.net because of security restrictions on client code but should be runnable in full trust).