Visualizing Folder Permissions

Overview

This blog post provides a comprehensive guide to exporting folder permissions to a JSON file using PowerShell and visualizing these permissions using Python. The process involves two main scripts:

Exporting Folder Permissions with PowerShell

This script retrieves and exports folder permissions from a specified directory and its subdirectories. It uses the Get-Acl cmdlet to gather access control information and stores this data in a custom PowerShell object. The collected permissions are then converted to JSON format and saved to a file. A limitation of this script to be aware of is that it won't work on paths greater than 260 characters.

 1# Define the folder path and output file path as variables
 2$FolderPath = "C:\Users\"
 3$OutputFile = "C:\Users\output.json"
 4
 5function ConvertTo-Binary {
 6    param (
 7        [int]$decimal
 8    )
 9    return [Convert]::ToString($decimal, 2).PadLeft(32, '0')
10}
11
12function Get-FolderPermissions {
13    param (
14        [string]$Path
15    )
16    
17    if (-Not (Test-Path -Path $Path)) {
18        Write-Warning "Path '$Path' does not exist."
19        return $null
20    }
21
22    $acl = Get-Acl -Path $Path
23    $permissions = @()
24
25    foreach ($access in $acl.Access) {
26        $rights = [int]$access.FileSystemRights
27
28        $bitWisePermissions = [PSCustomObject]@{
29            ReadData = $rights -band 1 # 2^0
30            CreateFiles = $rights -band 2 # 2^1
31            AppendData = $rights -band 4 # 2^2
32            ReadExtendedAttributes = $rights -band 8 # 2^3
33            WriteExtendedAttributes = $rights -band 16 # 2^4
34            ExecuteFile = $rights -band 32 # 2^5
35            DeleteSubfoldersAndFiles = $rights -band 64 # 2^6
36            ReadAttributes = $rights -band 128 # 2^7
37            WriteAttributes = $rights -band 256 # 2^8
38            Write = if (($rights -band 278) -eq 278) { $rights -band 278 } else { 0 } # Is a combination of other permissions
39            Delete = $rights -band 65536 # 2^16
40            ReadPermissions = $rights -band 131072 # 2^17
41            Read = if (($rights -band 131209) -eq 131209) { $rights -band 131209 } else { 0 } # Is a combination of other permissions
42            ReadAndExecute = if (($rights -band 131231) -eq 131231) { $rights -band 131231 } else { 0 } # Is a combination of other permissions
43            Modify = if (($rights -band 197055) -eq 197055) { $rights -band 197055 } else { 0 } # Is a combination of other permissions
44            ChangePermissions = $rights -band 262144 # 2^18
45            TakeOwnership = $rights -band 524288 # 2^19
46            Synchronize = $rights -band 1048576 # 2^20
47            FullControl = if (($rights -band 2032127) -eq 2032127) { $rights -band 2032127 } else { 0 } # Is a combination of other permissions
48        }
49
50        $permissions += [PSCustomObject]@{
51            AccessControlType = $access.AccessControlType
52            BitWisePermissionsBinary = ConvertTo-Binary($rights)
53            BitWisePermissions = $bitWisePermissions
54            BitwisePermissionsDecimal = $rights
55            IdentityReference = $access.IdentityReference
56            InheritanceFlags = $access.InheritanceFlags
57            IsInherited = $access.IsInherited
58            PropagationFlags = $access.PropagationFlags
59        }
60    }
61
62    return [PSCustomObject]@{
63        Path = $Path
64        Permissions = $permissions
65    }
66}
67
68function Get-AllFolderPermissions {
69    param (
70        [string]$RootPath
71    )
72    
73    $result = @()
74    $folders = Get-ChildItem -Path $RootPath -Recurse -Directory
75    
76    foreach ($folder in $folders) {
77        $folderPermissions = Get-FolderPermissions -Path $folder.FullName
78        if ($folderPermissions -ne $null) {
79            $result += $folderPermissions
80        }
81    }
82
83    $rootPermissions = Get-FolderPermissions -Path $RootPath
84    if ($rootPermissions -ne $null) {
85        $result += $rootPermissions
86    }
87
88    return $result
89}
90
91$allPermissions = Get-AllFolderPermissions -RootPath $FolderPath
92$json = $allPermissions | ConvertTo-Json -Depth 100
93
94# Save JSON without BOM
95$json | Out-File -FilePath $OutputFile -Encoding ascii
96
97Write-Host "Permissions saved to $OutputFile"

This is where I got the decimal values for ACL permissions from:

1[System.Enum]::GetValues([System.Security.AccessControl.FileSystemRights]) | ForEach-Object {
2    [PSCustomObject]@{
3        Name = $_
4        Value = [int]$_
5    }
6} | Format-Table -AutoSize

The result is:

 1                    ReadData       1
 2                    ReadData       1
 3                 CreateFiles       2
 4                 CreateFiles       2
 5                  AppendData       4
 6                  AppendData       4
 7      ReadExtendedAttributes       8
 8     WriteExtendedAttributes      16
 9                 ExecuteFile      32
10                 ExecuteFile      32
11DeleteSubdirectoriesAndFiles      64
12              ReadAttributes     128
13             WriteAttributes     256
14                       Write     278
15                      Delete   65536
16             ReadPermissions  131072
17                        Read  131209
18              ReadAndExecute  131241
19                      Modify  197055
20           ChangePermissions  262144
21               TakeOwnership  524288
22                 Synchronize 1048576
23                 FullControl 2032127

where each decimal represents a 32-bit binary integer.

ACL Permissions Table

PermissionDecimal ValueBinary Value
ReadData10000 0000 0000 0000 0000 0000 0000 0001
CreateFiles20000 0000 0000 0000 0000 0000 0000 0010
AppendData40000 0000 0000 0000 0000 0000 0000 0100
ReadExtendedAttributes80000 0000 0000 0000 0000 0000 0000 1000
WriteExtendedAttributes160000 0000 0000 0000 0000 0000 0001 0000
ExecuteFile320000 0000 0000 0000 0000 0000 0010 0000
DeleteSubdirectoriesAndFiles640000 0000 0000 0000 0000 0000 0100 0000
ReadAttributes1280000 0000 0000 0000 0000 0000 1000 0000
WriteAttributes2560000 0000 0000 0000 0000 0001 0000 0000
Write2780000 0000 0000 0000 0000 0001 0001 0110
Delete655360000 0000 0000 0001 0000 0000 0000 0000
ReadPermissions1310720000 0000 0000 0010 0000 0000 0000 0000
Read1312090000 0000 0000 0010 0000 0000 1000 1001
ReadAndExecute1312410000 0000 0000 0010 0000 0000 1010 0001
Modify1970550000 0000 0000 0011 0000 1000 1011 0111
ChangePermissions2621440000 0000 0000 0100 0000 0000 0000 0000
TakeOwnership5242880000 0000 0000 1000 0000 0000 0000 0000
Synchronize10485760000 0000 0001 0000 0000 0000 0000 0000
FullControl20321270000 0000 0001 1111 1111 1111 1111 1111

Understanding Bitwise Permissions

Bitwise permissions allow for efficient storage and manipulation of permissions using binary arithmetic. Each permission is represented by a specific bit in an integer, allowing multiple permissions to be combined into a single value. Here's a breakdown of the bitwise permissions used in the PowerShell script:

  • ReadData (2^0): Allows reading of file data.
  • CreateFiles (2^1): Allows creating files in a directory.
  • AppendData (2^2): Allows appending data to a file.
  • ReadExtendedAttributes (2^3): Allows reading extended file attributes.
  • WriteExtendedAttributes (2^4): Allows writing extended file attributes.
  • ExecuteFile (2^5): Allows executing a file.
  • DeleteSubfoldersAndFiles (2^6): Allows deleting subfolders and files.
  • ReadAttributes (2^7): Allows reading file attributes.
  • WriteAttributes (2^8): Allows writing file attributes.
  • Delete (2^16): Allows deleting a file or folder.
  • ReadPermissions (2^17): Allows reading file or folder permissions.
  • ChangePermissions (2^18): Allows changing file or folder permissions.
  • TakeOwnership (2^19): Allows taking ownership of a file or folder.
  • Synchronize (2^20): Synchronizes access to a file or folder.

These permissions are combined using bitwise OR operations and can be checked using bitwise AND operations, allowing for efficient permission management.

Combined Permissions Breakdown

Write (278)

The Write permission is a combination of the following:

  • WriteData (2^1) = 2
  • AppendData (2^2) = 4
  • WriteExtendedAttributes (2^4) = 16
  • WriteAttributes (2^8) = 256

Combining these with bitwise OR: [ 2 , | , 4 , | , 16 , | , 256 = 278 ]

Read (131209)

The Read permission is a combination of the following:

  • ReadData (2^0) = 1
  • ReadExtendedAttributes (2^3) = 8
  • ReadAttributes (2^7) = 128
  • ReadPermissions (2^17) = 131072

Combining these with bitwise OR: [ 1 , | , 8 , | , 128 , | , 131072 = 131209 ]

ReadAndExecute (131241)

The ReadAndExecute permission is a combination of Read and ExecuteFile:

  • Read (131209)
  • ExecuteFile (2^5) = 32

Combining these with bitwise OR: [ 131209 , | , 32 = 131241 ]

Modify (197055)

The Modify permission is a combination of ReadAndExecute, Write, and Delete:

  • ReadAndExecute (131241)
  • Write (278)
  • Delete (2^16) = 65536

Combining these with bitwise OR: [ 131241 , | , 278 , | , 65536 = 197055 ]

FullControl (2032127)

The FullControl permission includes all possible permissions:

  • ReadData (2^0) = 1
  • CreateFiles (2^1) = 2
  • AppendData (2^2) = 4
  • ReadExtendedAttributes (2^3) = 8
  • WriteExtendedAttributes (2^4) = 16
  • ExecuteFile (2^5) = 32
  • DeleteSubdirectoriesAndFiles (2^6) = 64
  • ReadAttributes (2^7) = 128
  • WriteAttributes (2^8) = 256
  • Delete (2^16) = 65536
  • ReadPermissions (2^17) = 131072
  • ChangePermissions (2^18) = 262144
  • TakeOwnership (2^19) = 524288
  • Synchronize (2^20) = 1048576

Combining these with bitwise OR: [ 1 , | , 2 , | , 4 , | , 8 , | , 16 , | , 32 , | , 64 , | , 128 , | , 256 , | , 65536 , | , 131072 , | , 262144 , | , 524288 , | , 1048576 = 2032127 ]

Visualizing Permissions with Python and Plotly

Once the permissions have been exported to a JSON file, we can use Python and Plotly to visualize this data. Plotly is a powerful visualization library that allows for the creation of interactive plots and charts.

The following Python script reads the JSON file generated by the PowerShell script and creates a treemap visualization of the folder permissions. The script uses Plotly's go.Treemap to create the treemap and applies a default theme for better aesthetics. Other themes can be found here.

  1import os
  2import json
  3import plotly.graph_objects as go
  4
  5# Function to replace backslashes in the username
  6def replace_backslashes(username):
  7    return username.replace('\\', '_')
  8
  9# Function to create the folder structure visualization for each user
 10def create_user_visualization(user_permissions, user_name, permission_types, output_dir):
 11    access_control_type = {0: "Allow", 1: "Deny"}
 12    inheritance_flags = {0: "None", 1: "ContainerInherit", 2: "ObjectInherit", 3: "ContainerInherit + ObjectInherit"}
 13    propagation_flags = {0: "None", 1: "InheritOnly", 2: "NoPropagateInherit"}
 14    
 15    figs = {}
 16    theme = "seaborn"  # Choose the theme you want to apply
 17    
 18    for permission_type in permission_types:
 19        labels = []
 20        parents = []
 21        texts = []
 22        display_labels = {}  # Map original labels to display labels
 23
 24        for path, permissions in user_permissions.items():
 25            if permissions.get(permission_type, 0) != 0:  # Only include paths with the specified permission type
 26                parts = path.split('\\')
 27                for i in range(len(parts)):
 28                    original_label = '\\'.join(parts[:i+1])
 29                    display_label = parts[i]
 30                    
 31                    if i > 0:
 32                        display_label = '.\\' + display_label  # Prefix with .\ if it has a parent node
 33                    if i < len(parts) - 1:
 34                        display_label += '\\'  # Append \ if it has a child node
 35                    
 36                    display_labels[original_label] = display_label
 37                    
 38                    if display_label not in labels:
 39                        labels.append(display_label)
 40                        if i == 0:
 41                            parents.append("")
 42                        else:
 43                            parent_label = '\\'.join(parts[:i])
 44                            parent_display_label = display_labels[parent_label]
 45                            parents.append(parent_display_label)
 46                        
 47                        access_control_type_value = permissions.get('AccessControlType', 'Unknown')
 48                        inheritance_flags_value = permissions.get('InheritanceFlags', 'Unknown')
 49                        propagation_flags_value = permissions.get('PropagationFlags', 'Unknown')
 50                        is_inherited = permissions.get('IsInherited', 'Unknown')
 51                        bitwise_permissions_decimal = permissions.get('BitwisePermissionsDecimal', 'Unknown')
 52                        bitwise_permissions_binary = permissions.get('BitWisePermissionsBinary', 'Unknown').lstrip('0')
 53
 54                        texts.append(
 55                            f"AccessControlType: {access_control_type.get(access_control_type_value, 'Unknown')}<br>"
 56                            f"InheritanceFlags: {inheritance_flags.get(inheritance_flags_value, 'Unknown')}<br>"
 57                            f"PropagationFlags: {propagation_flags.get(propagation_flags_value, 'Unknown')}<br>"
 58                            f"IsInherited: {is_inherited}<br>"
 59                            f"BitwisePermissions (Decimal): {bitwise_permissions_decimal}<br>"
 60                            f"BitwisePermissions (Binary): {bitwise_permissions_binary}"
 61                        )
 62
 63        if not labels:
 64            continue
 65
 66        fig = go.Figure(go.Treemap(
 67            labels=labels,
 68            parents=parents,
 69            text=texts,
 70            hoverinfo="label+text"
 71        ))
 72
 73        fig.update_layout(title_text=f"{permission_type} Permissions for {user_name}", template=theme)
 74        figs[permission_type] = fig
 75    
 76    # Create a dropdown menu to select graphs
 77    dropdown_buttons = [
 78        {'label': perm, 'method': 'update', 'args': [{'visible': [perm == p for p in permission_types]}, {'title': f"{perm} Permissions for {user_name}"}]}
 79        for perm in permission_types
 80    ]
 81
 82    if figs:
 83        final_fig = go.Figure()
 84        for perm in permission_types:
 85            if perm in figs:
 86                final_fig.add_traces(figs[perm].data)
 87                final_fig.update_traces(visible=False)
 88        final_fig.data[0].visible = True
 89
 90        final_fig.update_layout(
 91            updatemenus=[
 92                {
 93                    'buttons': dropdown_buttons,
 94                    'direction': 'down',
 95                    'showactive': True,
 96                }
 97            ],
 98            title_text=f"Permissions for {user_name}",
 99            template=theme
100        )
101
102        os.makedirs(output_dir, exist_ok=True)
103        sanitized_user_name = replace_backslashes(user_name)
104        final_fig.write_html(os.path.join(output_dir, f"{sanitized_user_name}.html"))
105
106# Main function to process the JSON data and generate visualizations
107def main():
108    input_file = 'permissions.json'
109    output_dir = 'folder_permissions_treemap'
110    
111    # Load the JSON data
112    with open(input_file, 'r') as file:
113        data = json.load(file)
114
115    # Extract permission types
116    sample_permission = data[0]['Permissions'][0]['BitWisePermissions']
117    permission_types = list(sample_permission.keys())
118
119    # Process the data to group permissions by user
120    user_permissions = {}
121    for entry in data:
122        path = entry['Path']
123        for permission in entry['Permissions']:
124            user = permission['IdentityReference']['Value']
125            if user not in user_permissions:
126                user_permissions[user] = {}
127            user_permissions[user][path] = permission['BitWisePermissions']
128            user_permissions[user][path].update({
129                'AccessControlType': permission['AccessControlType'],
130                'InheritanceFlags': permission['InheritanceFlags'],
131                'PropagationFlags': permission['PropagationFlags'],
132                'IsInherited': permission['IsInherited'],
133                'BitwisePermissionsDecimal': permission.get('BitwisePermissionsDecimal', 'Unknown'),
134                'BitWisePermissionsBinary': permission.get('BitWisePermissionsBinary', 'Unknown')
135            })
136
137    # Create visualizations for each user
138    total_users = len(user_permissions)
139    for idx, (user, permissions) in enumerate(user_permissions.items(), start=1):
140        print(f"Processing user {idx} of {total_users}: {user}")
141        create_user_visualization(permissions, user, permission_types, output_dir)
142        print(f"Finished processing user {idx} of {total_users}: {user}")
143
144if __name__ == "__main__":
145    main()

This approach provides a clear and structured method for administrators to audit and visualize folder permissions in their environment.