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