There are a few simple built-in .Net techniques for checking to see if the current user has read/write permission against a file. However, if you want to see if *somebody else* has permissions, then none of the built-in .Net functions are gonna be of much use. You're forced into the dark world of Windows APIs.
Let's start with how permissions work behind the scenes. The NTFS file system attaches a collection of Access Control Entries (ACE) to each file and directory. The ACE includes all of the stuff you're probably already familiar with when setting file permissions manually... The User Name, the Permission (i.e. Modify), and whether or not this entry is an "allow" or "deny". This collection of ACEs is called the Discretionary Access Control List (DACL).
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
The functions that retrieve the DACL, read the ACEs, and compare the permissions are all Windows API calls. So let's start with declaring all of our API stuff:
Imports System.Runtime.InteropServices Imports System.ComponentModel <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _ Private Structure TRUSTEE Dim pMultipleTrustee As IntPtr Dim MultipleTrusteeOperation As Integer Dim TrusteeForm As Integer Dim TrusteeType As Integer Dim ptstrName As IntPtr End Structure Private Enum SE_OBJECT_TYPE As Integer SE_UNKNOWN_OBJECT_TYPE = 0 SE_FILE_OBJECT SE_SERVICE SE_PRINTER SE_REGISTRY_KEY SE_LMSHARE SE_KERNEL_OBJECT SE_WINDOW_OBJECT SE_DS_OBJECT SE_DS_OBJECT_ALL SE_PROVIDER_DEFINED_OBJECT SE_WMIGUID_OBJECT SE_REGISTRY_WOW64_32 End Enum Private Enum SECURITY_INFORMATION As Integer OWNER_SECURITY_INFORMATION = 1 GROUP_SECURITY_INFORMATION = 2 DACL_SECURITY_INFORMATION = 4 SACL_SECURITY_INFORMATION = 8 PROTECTED_SACL_SECURITY_INFORMATION = 16 PROTECTED_DACL_SECURITY_INFORMATION = 32 UNPROTECTED_SACL_SECURITY_INFORMATION = 64 UNPROTECTED_DACL_SECURITY_INFORMATION = 128 End Enum Private Declare Auto Sub BuildTrusteeWithSid Lib "advapi32.dll" ( _ ByVal pTrustee As IntPtr, _ ByVal pSid As IntPtr _ ) Private Declare Auto Function GetNamedSecurityInfo Lib "advapi32.dll" ( _ ByVal pObjectName As String, _ ByVal ObjectType As SE_OBJECT_TYPE, _ ByVal SecurityInfo As SECURITY_INFORMATION, _ ByRef ppsidOwner As IntPtr, _ ByRef ppsidGroup As IntPtr, _ ByRef ppDacl As IntPtr, _ ByRef ppSacl As IntPtr, _ ByRef ppSecurityDescriptor As IntPtr _ ) As Integer Private Declare Auto Function GetEffectiveRightsFromAcl Lib "advapi32.dll" ( _ ByVal pacl As IntPtr, _ ByVal pTrustee As IntPtr, _ ByRef pAccessRights As Integer _ ) As Integer Private Declare Auto Function LookupAccountName Lib "advapi32.dll" ( _ ByVal lpSystemName As String, _ ByVal lpAccountName As String, _ ByVal Sid As IntPtr, _ ByRef cbSid As Integer, _ ByVal lpReferenceDomainName As String, _ ByRef cchReferencedDomainName As Integer, _ ByRef peUse As Integer _ ) As Boolean
API Documentation Links
The Example Code
The next step is to build a "Trustee"... (a Trustee is the internal name for the User/Group that is found inside an ACE). This requires that we use LookupAccountName to retrieve the Security Identifier (SID) of the user account being used to test the permissions. Next we use BuildTrusteeWithSid to convert the SID into the Trustee structure
Dim pTrustee, pSID As IntPtr Dim Domain As String Dim lenDomain, lenSid, peUse, LastError As Integer Dim Win32Error As Win32Exception Dim t As TRUSTEE ' do a "dry run" to get the size of the SID and Domain string LookupAccountName(Nothing, UserName, Nothing, lenSid, Nothing, lenDomain, peUse) Domain = Space(lenDomain) pSID = Marshal.AllocHGlobal(lenSid) ' do it again, for real this time If LookupAccountName(Nothing, UserName, pSID, lenSid, Domain, lenDomain, peUse) _ = False Then LastError = Marshal.GetLastWin32Error() Win32Error = New Win32Exception(LastError) Throw New Exception(Win32Error.Message) End If ' Build a trustee pTrustee = Marshal.AllocHGlobal(Marshal.SizeOf(t)) BuildTrusteeWithSid(pTrustee, pSID)
OK, now we need to get the DACL from the file. The DACL is found inside the Security Descriptor for each file (on an NTFS partition). The GetNamedSecurityInfo API is used to retrieve both the Security Descriptor and the DACL.
Dim pDACL, pSD As IntPtr Dim AccessMask, Mask, ret As Integer Dim Win32Error As Win32Exception ' Get the DACL from the file ret = GetNamedSecurityInfo(Path, SE_OBJECT_TYPE.SE_FILE_OBJECT, _ SECURITY_INFORMATION.DACL_SECURITY_INFORMATION, Nothing, Nothing, _ pDACL, Nothing, pSD) If ret <> 0 Then Win32Error = New Win32Exception(ret) Throw New Exception(Win32Error.Message) End If
The last step is to use GetEffectiveRightsFromAcl to get all of the permissions that the user is allowed to perform based upon the DACL from the file. Then we compare this "permissions mask" that we just generated to the numeric equivalent of the permission that you provided (i.e. Read = 1179785). If the two masks match, that permission is allowed.
' Get the Access Mask using the supplied user account ret = GetEffectiveRightsFromAcl(pDACL, pTrustee, Mask) If ret <> 0 Then Marshal.FreeHGlobal(pSD) Win32Error = New Win32Exception(ret) Throw New Exception(Win32Error.Message) End If ' We don't want a memory leak! Marshal.FreeHGlobal(pSD) ' Let's see if we've got a match! If (AccessMask And Mask) = AccessMask Then Return True Else Return False End If
Downloads/Links
Read a related article on Setting File Permissions
Download the complete VB.Net Source code example used in this article:
CheckPerm.zip