Setting File Permissions

When it comes to working with setting access permission, there are 4 generally acceptable methods to use... Use Windows Management Instrumentation (WMI), Use the ADsSecurity.dll from the ASDI SDK, use the low-level APIs, or use the high-level APIs.

Each technique has its strengths and weakness... WMI is clumsy, somewhat bloated, and slow; ADsSecurity doesn't order the ACEs correctly, doesn't support NTFS inheritance, and is slow; the low-level APIs are fast but tedious and have the same problems as the ADsSecurity method. That leaves the high-level APIs as the choice that most programmers use (although its weakness is that it only works with Win2k and above).

Note: The weaknesses of "ordering ACEs" and "supporting inheritance" mentioned above only affect NTFS file systems on Win2k and above... and can be overcome with additional programming. Other uses of these security methods (on registry keys, Active Directory object, etc.) are unaffected by these weaknesses and therefore are safe to use.

So, when dealing setting permissions on files on NTFS partitions, I'd recommend the high-level API approach using BuildExplicitAccessWithName and SetEntriesInAcl.

Permissions are recorded in the ACE as numbers, so we need the following table of commonly-used permissions and their numeric equivalents.

Full Control = 20321127
Modify = 1245631
Read & Execute = 1179817
Read = 1179785
Write = 1179926
Execute = 1179808

API Declarations

So, let's start with the required API "boiler plate":

Imports System.Runtime.InteropServices
Imports System.IO
Imports System.ComponentModel

' Type of Securable Object we are using
Private Const SE_FILE_OBJECT As Integer = 1

' The Security Information constants required
Private Const DACL_SECURITY_INFORMATION As Integer  = 4
Private Const SET_ACCESS As Integer  = 2

' Standard access rights extracted from WinNT.h
Private Const SYNCHRONIZE As Integer  = &H100000
Private Const READ_CONTROL As Integer  = &H20000
Private Const WRITE_DAC As Integer  = &H40000
Private Const WRITE_OWNER As Integer  = &H80000
Private Const STANDARD_RIGHTS_READ As Integer  = (READ_CONTROL)
Private Const STANDARD_RIGHTS_WRITE  As Integer = (READ_CONTROL)
Private Const DELETE As Integer  = &H10000
Private Const DELETE_CHILD As Integer  = &H40
Private Const ALL_ACCESS As Integer  = &HF0000 Or SYNCHRONIZE Or &H1FF

' Generic access rights extracted from WinNT.h
Private Const GENERIC_ALL As Integer  = &H10000000
Private Const GENERIC_EXECUTE  As Integer = &H20000000
Private Const GENERIC_READ As Integer  = &H80000000
Private Const GENERIC_WRITE As Integer  = &H40000000

' Inheritance Flags
Private Const CONTAINER_INHERIT_ACE As Integer  = 2
Private Const OBJECT_INHERIT_ACE As Integer  = 1

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
Public Structure TRUSTEE
    Dim pMultipleTrustee As IIntPtr
    Dim MultipleTrusteeOperation As Integer
    Dim TrusteeForm As Integer
    Dim TrusteeType As Integer
    Dim ptstrName As String
End Structure

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
Public Structure EXPLICIT_ACCESS
    Dim grfAccessPermissions As Integer
    Dim grfAccessMode As Integer
    Dim grfInheritance As Integer
    Dim Trustee As Trustee
End Structure

Declare Auto Sub BuildExplicitAccessWithName Lib "AdvAPI32.DLL" ( _
    ByRef pExplicitAccess As EXPLICIT_ACCESS, _
    ByVal pTrusteeName As String, _
    ByVal AccessPermissions As Integer, _
    ByVal AccessMode As Integer, _
    ByVal Inheritance As Integer _
)

Declare Auto Function SetEntriesInAcl Lib "AdvAPI32.DLL" ( _
    ByVal cCountOfExplicitEntries As Integer, _
    ByRef pListOfExplicitEntries As EXPLICIT_ACCESS, _
    ByVal OldAcl As IntPtr, _
    ByRef NewAcl As IntPtr _
) As Integer

Declare Auto Function GetNamedSecurityInfo Lib "AdvAPI32.DLL" ( _
    ByVal pObjectName As String, _
    ByVal ObjectType As Integer, _
    ByVal SecurityInfo As Integer, _
    ByRef ppsidOwner As IntPtr, _
    ByRef ppsidGroup As IntPtr, _
    ByRef ppDacl As IntPtr, _
    ByRef ppSacl As IntPtr, _
    ByRef ppSecurityDescriptor As IntPtr _
) As Integer

Declare Auto Function SetNamedSecurityInfo Lib "AdvAPI32.DLL" ( _
    ByVal pObjectName As String, _
    ByVal ObjectType As Integer, _
    ByVal SecurityInfo As Integer, _
    ByVal psidOwner As IntPtr, _
    ByVal psidGroup As IntPtr, _
    ByVal pDacl As IntPtr, _
    ByVal pSacl As IntPtr _
) As Integer

API Documentation Links

The Example Code

In order to change the permissions on a file, we'll first need to use GetNamedSecurityInfo to get the existing permissions from the file. Permissions are contained in a Discretionary Access Control List (DACL) which is held inside a Security Descriptor associated with each file on an NTFS partition.

Next we use BuildExplicitAccessWithName to create an "Explicit Access" structure that contains the information that we're using to set the permissions (the User Name, the numeric equivalent of the read/write permission, and the permissions inheritance). Note: Inheritance really only applies to directories, but there's no harm in including those flags for use with files

The next step is to use SetEntriesInAcl to *merge* the new Explicit Access structure into the existing DACL and create a new DACL. Notice that we are not really replacing the existing DACL with a completely new DACL... we are merely adding a new Access Control Entry (ACE), or editing an existing ACE, inside the DACL.

The last step is to write the newly modified DACL back to the file using the SetNamedSecurityInfo API.

' get the Security Descriptor and DACL
ret = GetNamedSecurityInfo(strPath, SE_FILE_OBJECT, _
 DACL_SECURITY_INFORMATION, Nothing, Nothing, pOldDACL, Nothing, Nothing)
If ret <> 0 Then
    Win32Error = New Win32Exception(ret)
    Throw New Exception(Win32Error.Message)
End If

' build an explicit access structure
BuildExplicitAccessWithName(ea, strUserName, iPerm, SET_ACCESS, _
 CONTAINER_INHERIT_ACE Or OBJECT_INHERIT_ACE)

' merge this Explicit Access with the existing DACL
ret = SetEntriesInAcl(1, ea, pOldDACL, pNewDACL)
If ret <> 0 Then
    Win32Error = New Win32Exception(ret)
    Throw New Exception(Win32Error.Message)
End If

' write the new Security Descriptor/DACL back
ret = SetNamedSecurityInfo(strPath, SE_FILE_OBJECT, _
 DACL_SECURITY_INFORMATION, Nothing, Nothing, pNewDACL, Nothing)
If ret <> 0 Then
    Win32Error = New Win32Exception(ret)
    Throw New Exception(Win32Error.Message)
End If

' clean up and go home
Marshal.FreeHGlobal(pNewDACL)

Notice the use of FreeHGlobal() to release the memory that we allocated during the use of the APIs.

Downloads/Links

Read a related article on Checking File Permission
Download the complete VB.Net Source code example used in this article: SetPerm.zip