HOWTO: Add an Access-Allowed ACE to a File Through Visual Basic

ID: Q194757


The information in this article applies to:


SUMMARY

The purpose of an Access-Allowed Access Control Entry (ACE) is to either permit or deny access to a specific file to a user or a group of users. This article contains a sample function that adds a new ACE to a text file each time the function is called.


MORE INFORMATION

WARNING: One or more of the following functions are discussed in this article; VarPtr, VarPtrArray, VarPtrStringArray, StrPtr, ObjPtr. These functions are not supported by Microsoft Technical Support. They are not documented in the Visual Basic documentation and are provided in this Knowledge Base article "as is." Microsoft does not guarantee that they will be available in future releases of Visual Basic.

NOTE: Access Controls Entries and Security Descriptors are very powerful,low-level objects, and should only be handled by programmers who have a thorough understanding of file security.

This sample only works on NT 4.0 machines with NTFS-formatted drives. Fat16-formatted drives do not offer the same security features.

The steps to create this example are below; the following APIs are used:


   GetComputerName
   GetUserName
   LookupAccountName
   InitializeSecurityDescriptor
   GetSecurityDescriptorDacl
   SetSecurityDescriptorDacl
   GetFileSecurity
   SetFileSecurity
   InitializeAcl
   GetAclInformation
   GetAce
   AddAce
   AddAccessAllowedAce
   GetLengthSid
   CopyMemory 

Step-by-Step Example

  1. Create a Standard EXE project in Visual Basic. Form1 is created by default.


  2. Add a CommandButton to Form1.


  3. Paste the following code into Form1's code window:


  4. 
          Option Explicit
    
          Private Sub Command1_Click()
             PerformACESample
          End Sub 
  5. Add a standard module (Module1) to this project.


  6. In the standard module (Module1), paste the following code:


  7. 
          Option Explicit
    
          ' Constants used within our API calls. Refer to the MSDN for more
          ' information on how/what these constants are used for.
    
          ' Memory constants used through various memory API calls.
          Public Const GMEM_MOVEABLE = &H2
          Public Const LMEM_FIXED = &H0
          Public Const LMEM_ZEROINIT = &H40
          Public Const LPTR = (LMEM_FIXED + LMEM_ZEROINIT)
          Public Const GENERIC_READ = &H80000000
    
          ' The file/security API call constants.
          ' Refer to the MSDN for more information on how/what these constants
          ' are used for.
          Public Const DACL_SECURITY_INFORMATION = &H4
          Public Const SECURITY_DESCRIPTOR_REVISION = (1)
          Public Const SECURITY_DESCRIPTOR_MIN_LENGTH = (20)
          Public Const SD_SIZE = (65536 + SECURITY_DESCRIPTOR_MIN_LENGTH)
          Public Const ACL_REVISION2 = (2)
          Public Const ACL_REVISION = (2)
          Public Const MAXDWORD = &HFFFF
          Public Const SidTypeUser = 1
          Public Const AclSizeInformation = 2
    
          ' Structures used by our API calls.
          ' Refer to the MSDN for more information on how/what these
          ' structures are used for.
          Type ACE_HEADER
             AceType As Byte
             AceFlags As Byte
             AceSize As Integer
          End Type
    
          Type ACCESS_ALLOWED_ACE
             Header As ACE_HEADER
             Mask As Long
             SidStart As Long
          End Type
    
          Type ACL
             AclRevision As Byte
             Sbz1 As Byte
             AclSize As Integer
             AceCount As Integer
             Sbz2 As Integer
          End Type
    
          Type ACL_SIZE_INFORMATION
             AceCount As Long
             AclBytesInUse As Long
             AclBytesFree As Long
          End Type
    
          Type SECURITY_DESCRIPTOR
             Revision As Byte
             Sbz1 As Byte
             Control As Long
             Owner As Long
             Group As Long
             sACL As ACL
             Dacl As ACL
          End Type
    
          ' API calls used within this sample. Refer to the MSDN for more
          ' information on how/what these APIs do.
    
          Declare Function GetComputerName Lib "kernel32" Alias _
             "GetComputerNameA" (ByVal lpBuffer As String, _
             nSize As Long) As Long
    
          Declare Function GetUserName Lib "advapi32.dll" Alias _
             "GetUserNameA" (ByVal lpBuffer As String, nSize As Long) As Long
    
          Declare Function LookupAccountName Lib "advapi32.dll" Alias _
             "LookupAccountNameA" (lpSystemName As String, _
             ByVal lpAccountName As String, Sid As Any, cbSid As Long, _
             ByVal ReferencedDomainName As String, _
             cbReferencedDomainName As Long, peUse As Long) As Long
    
          Declare Function InitializeSecurityDescriptor Lib "advapi32.dll" _
             (pSecurityDescriptor As SECURITY_DESCRIPTOR, _
             ByVal dwRevision As Long) As Long
    
          Declare Function GetSecurityDescriptorDacl Lib "advapi32.dll" _
             (pSecurityDescriptor As Byte, lpbDaclPresent As Long, _
             pDacl As Long, lpbDaclDefaulted As Long) As Long
    
          Declare Function GetFileSecurityN Lib "advapi32.dll" Alias _
             "GetFileSecurityA" (ByVal lpFileName As String, _
             ByVal RequestedInformation As Long, _
             ByVal pSecurityDescriptor As Long, ByVal nLength As Long, _
             lpnLengthNeeded As Long) As Long
    
          Declare Function GetFileSecurity Lib "advapi32.dll" Alias _
             "GetFileSecurityA" (ByVal lpFileName As String, _
             ByVal RequestedInformation As Long, _
             pSecurityDescriptor As Byte, ByVal nLength As Long, _
             lpnLengthNeeded As Long) As Long
    
          Declare Function GetAclInformation Lib "advapi32.dll" _
             (ByVal pAcl As Long, pAclInformation As Any, _
             ByVal nAclInformationLength As Long, _
             ByVal dwAclInformationClass As Long) As Long
    
          Declare Function GetLengthSid Lib "advapi32.dll" (pSid As Any) As _
             Long
    
          Declare Function InitializeAcl Lib "advapi32.dll" (pAcl As Byte, _
             ByVal nAclLength As Long, ByVal dwAclRevision As Long) As Long
    
          Declare Function GetAce Lib "advapi32.dll" (ByVal pAcl As Long, _
             ByVal dwAceIndex As Long, pace As Any) As Long
    
          Declare Function AddAce Lib "advapi32.dll" (ByVal pAcl As Long, _
             ByVal dwAceRevision As Long, ByVal dwStartingAceIndex As Long, _
             ByVal pAceList As Long, ByVal nAceListLength As Long) As Long
    
          Declare Function AddAccessAllowedAce Lib "advapi32.dll" _
             (pAcl As Byte, ByVal dwAceRevision As Long, _
             ByVal AccessMask As Long, pSid As Byte) As Long
    
          Declare Function SetSecurityDescriptorDacl Lib "advapi32.dll" _
             (pSecurityDescriptor As SECURITY_DESCRIPTOR, _
             ByVal bDaclPresent As Long, pDacl As Byte, _
             ByVal bDaclDefaulted As Long) As Long
    
          Declare Function SetFileSecurity Lib "advapi32.dll" Alias _
             "SetFileSecurityA" (ByVal lpFileName As String, _
             ByVal SecurityInformation As Long, _
             pSecurityDescriptor As SECURITY_DESCRIPTOR) As Long
    
          Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
             (hpvDest As Any, ByVal hpvSource As Long, ByVal cbCopy As Long)
    
          Public Sub PerformACESample()
             Dim lResult As Long            ' Result of various API calls.
             Dim I As Integer               ' Used in looping.
             Dim bUserSid(255) As Byte      ' This will contain your SID.
             Dim sSystemName As String      ' Name of this computer system.
    
             Dim lSystemNameLength As Long  ' Length of string that contains
                                            ' the name of this system.
    
             Dim lLengthUserName As Long    ' Max length of user name.
    
             Dim sUserName As String * 255  ' String to hold the current user
                                            ' name.
    
             Dim lUserSID As Long           ' Used to hold the SID of the
                                            ' current user.
    
             Dim lUserSIDSize As Long          ' Size of the SID.
             Dim sDomainName As String * 255   ' Domain the user belongs to.
             Dim lDomainNameLength As Long     ' Length of domain name needed.
    
             Dim lSIDType As Long              ' The type of SID info we are
                                               ' getting back.
    
             Dim sFileSD As SECURITY_DESCRIPTOR   ' SD of the file we want.
    
             Dim bSDBuf() As Byte           ' Buffer that holds the security
                                            ' descriptor for this file.
    
             Dim lFileSDSize As Long           ' Size of the File SD.
             Dim lSizeNeeded As Long           ' Size needed for SD for file.
             Dim sFileName As String           ' String to hold the file we
                                               ' are playing with.
    
             Dim sNewSD As SECURITY_DESCRIPTOR ' New security descriptor.
    
             Dim sACL As ACL                   ' Used in grabbing the DACL from
                                               ' the File SD.
    
             Dim lDaclPresent As Long          ' Used in grabbing the DACL from
                                               ' the File SD.
    
             Dim lDaclDefaulted As Long        ' Used in grabbing the DACL from
                                               ' the File SD.
    
             Dim sACLInfo As ACL_SIZE_INFORMATION  ' Used in grabbing the ACL
                                                   ' from the File SD.
    
             Dim lACLSize As Long           ' Size of the ACL structure used
                                            ' to get the ACL from the File SD.
    
             Dim pAcl As Long               ' Current ACL for this file.
             Dim lNewACLSize As Long        ' Size of new ACL to create.
             Dim bNewACL() As Byte          ' Buffer to hold new ACL.
    
             Dim sCurrentACE As ACCESS_ALLOWED_ACE    ' Current ACE.
             Dim pCurrentAce As Long                  ' Our current ACE.
    
             ' The first action taken is acquiring the name of the user
             ' who is currently logged onto this system. Take the user's
             ' name and grab its companion SID for future use.
             ' Use the GetUserName API to find out who is currently logged onto
             ' this system. Preset the length of the string to hold the
             ' returned user name from the "GetUserName" API.
             lLengthUserName = 255
             sUserName = Space(lLengthUserName)
    
             ' Call GetUserName to find out who is logged onto this system.
             lResult = GetUserName(sUserName, lLengthUserName)
    
             ' Return value of zero means the call failed; test for this before
             ' continuing.
             If (lResult = 0) Then
                MsgBox "Error: Unable to Retrieve the Current User Name"
                Exit Sub
             End If
    
             ' You now have the user name of the person who is logged onto this
             ' system. Using that information, get the SID of the user. (Refer
             ' to the MSDN for more information on SIDs and their
             ' function/purpose in the operating system.) Get the SID of this
             ' user by using the LookupAccountName API. In order to use the SID
             ' of the current user account, call the LookupAccountName API
             ' twice. The first time is to get the required sizes of the SID
             ' and the DomainName string. The second call is to actually get
             ' the desired information.
    
             lResult = LookupAccountName(vbNullString, sUserName, _
                bUserSid(0), 255, sDomainName, lDomainNameLength, _
                lSIDType)
    
             ' Now set the sDomainName string buffer to its proper size before
             ' calling the API again.
             sDomainName = Space(lDomainNameLength)
    
             ' Call the LookupAccountName again to get the actual SID for user.
             lResult = LookupAccountName(vbNullString, sUserName, _
                bUserSid(0), 255, sDomainName, lDomainNameLength, _
                lSIDType)
    
             ' Return value of zero means the call to LookupAccountName failed;
             ' test for this before you continue.
               If (lResult = 0) Then
                  MsgBox "Error: Unable to Lookup the Current User Account: " _
                     & sUserName
                  Exit Sub
               End If
    
             ' You now have the SID for the user who is logged on.
             ' The SID is of interest since it will get the security descriptor
             ' for the file that the user is interested in. For this
             ' example, assume that the user is interested in
             ' the C:\README.TXT file.
    
             ' Set the name of the text file used in this sample:
             ' Readme.txt.
             sFileName = "C:\Readme.Txt"
             lFileSDSize = Len(sFileSD)
    
             ' The GetFileSecurity API will retrieve the Security Descriptor
             ' for the file. However, you must call this API twice: once to get
             ' the proper size for the Security Descriptor and once to get the
             ' actual Security Descriptor information.
    
             lResult = GetFileSecurityN(sFileName, DACL_SECURITY_INFORMATION, _
                0, 0, lSizeNeeded)
    
             ' Redimension the Security Descriptor buffer to the proper size.
             ReDim bSDBuf(lSizeNeeded)
    
             ' Now get the actual Security Descriptor for the file.
             lResult = GetFileSecurity(sFileName, DACL_SECURITY_INFORMATION, _
                bSDBuf(0), lSizeNeeded, lSizeNeeded)
    
             ' A return code of zero means the call failed; test for this
             ' before continuing.
             If (lResult = 0) Then
                MsgBox "Error: Unable to Get the File Security Descriptor"
                Exit Sub
             End If
    
             ' Call InitializeSecurityDescriptor to build a new SD for the
             ' file.
             lResult = InitializeSecurityDescriptor(sNewSD, _
                SECURITY_DESCRIPTOR_REVISION)
    
             ' A return code of zero means the call failed; test for this
             ' before continuing.
             If (lResult = 0) Then
                MsgBox "Error: Unable to Initialize New Security Descriptor"
                Exit Sub
             End If
    
             ' You now have the file's SD and a new Security Descriptor
             ' that will replace the current one. Next, pull the DACL from
             ' the SD. To do so, call the GetSecurityDescriptorDacl API
             ' function.
    
             lResult = GetSecurityDescriptorDacl(bSDBuf(0), lDaclPresent, _
                pAcl, lDaclDefaulted)
    
             ' A return code of zero means the call failed; test for this
             ' before continuing.
             If (lResult = 0) Then
                MsgBox "Error: Unable to Get DACL from File Security " _
                   & "Descriptor"
                Exit Sub
             End If
    
             ' You have the file's SD, and want to now pull the ACL from the
             ' SD. To do so, call the GetACLInformation API function.
             ' See if ACL exists for this file before getting the ACL
             ' information.
             If (lDaclPresent = False) Then
                MsgBox "Error: No ACL Information Available for this File"
                Exit Sub
             End If
    
             ' Attempt to get the ACL from the file's Security Descriptor.
             lResult = GetAclInformation(pAcl, sACLInfo, Len(sACLInfo), 2&)
    
             ' A return code of zero means the call failed; test for this
             ' before continuing.
             If (lResult = 0) Then
                MsgBox "Error: Unable to Get ACL from File Security Descriptor"
                Exit Sub
             End If
    
             ' Now that you have the ACL information, compute the new ACL size
             ' requirements.
             lNewACLSize = sACLInfo.AclBytesInUse + Len(sCurrentACE) + _
                GetLengthSid(bUserSid(0)) - 4
    
             ' Resize our new ACL buffer to its proper size.
             ReDim bNewACL(lNewACLSize)
    
             ' Use the InitializeAcl API function call to initialize the new
             ' ACL.
             lResult = InitializeAcl(bNewACL(0), lNewACLSize, ACL_REVISION2)
    
             ' A return code of zero means the call failed; test for this
             ' before continuing.
             If (lResult = 0) Then
                MsgBox "Error: Unable to Initialize New ACL"
                Exit Sub
             End If
    
             ' If a DACL is present, copy it to a new DACL.
             If (lDaclPresent) Then
    
                ' Copy the ACEs from the file to the new ACL.
                If (sACLInfo.AceCount > 0) Then
    
                   ' Grab each ACE and stuff them into the new ACL.
                   For I = 0 To (sACLInfo.AceCount - 1)
    
                      ' Attempt to grab the next ACE.
                      lResult = GetAce(pAcl, I, pCurrentAce)
    
                      ' Make sure you have the current ACE under question.
                      If (lResult = 0) Then
                         MsgBox "Error: Unable to Obtain ACE (" & I & ")"
                         Exit Sub
                      End If
    
                      ' You have a pointer to the ACE. Place it
                      ' into a structure, so you can get at its size.
                      CopyMemory sCurrentACE, pCurrentAce, LenB(sCurrentACE)
    
                      ' Now that you have the ACE, add it to the new ACL.
                      lResult = AddAce(VarPtr(bNewACL(0)), ACL_REVISION, _
                         MAXDWORD, pCurrentAce, _
                         sCurrentACE.Header.AceSize)
    
                      ' Make sure you have the current ACE under question.
                      If (lResult = 0) Then
                         MsgBox "Error: Unable to Add ACE to New ACL"
                         Exit Sub
                      End If
                   Next I
    
                   ' You have now rebuilt a new ACL and want to add it to
                   ' the newly created DACL.
                   lResult = AddAccessAllowedAce(bNewACL(0), ACL_REVISION2, _
                      GENERIC_READ, bUserSid(0))
    
                   ' Make sure added the ACL to the DACL.
                   If (lResult = 0) Then
                      MsgBox "Error: Unable to Add ACL to DACL"
                      Exit Sub
                   End If
    
                   ' Set the file's Security Descriptor to the new DACL.
                   lResult = SetSecurityDescriptorDacl(sNewSD, 1, _
                      bNewACL(0), 0)
    
                   ' Make sure you set the SD to the new DACL.
                   If (lResult = 0) Then
                      MsgBox "Error: " & _
                          "Unable to Set New DACL to Security Descriptor"
                      Exit Sub
                   End If
    
                   ' The final step is to add the Security Descriptor back to
                   ' the file!
                   lResult = SetFileSecurity(sFileName, _
                      DACL_SECURITY_INFORMATION, sNewSD)
    
                   ' Make sure you added the Security Descriptor to the file!
                   If (lResult = 0) Then
                      MsgBox "Error: Unable to Set New Security Descriptor " _
                         & " to File : " & sFileName
                   Else
                      MsgBox "Updated Security Descriptor on File: " _
                         & sFileName
                   End If
    
                   ' Finally, show the current ACE count for this file.
                   lResult = GetFileSecurityN(sFileName, _
                      DACL_SECURITY_INFORMATION, 0, 0, lSizeNeeded)
    
                   ' Redimension the Security Descriptor buffer variable to the
                   ' proper size.
                   ReDim bSDBuf(lSizeNeeded)
    
                   ' Now get the actual Security Descriptor for the file.
                   lResult = GetFileSecurity(sFileName, _
                      DACL_SECURITY_INFORMATION, _
                      bSDBuf(0), lSizeNeeded, lSizeNeeded)
    
                   ' Make sure you've got the SD for this file!
                   If (lResult = 0) Then
                      MsgBox "Error: Unable to Get the File Security " _
                         & "Descriptor"
                      Exit Sub
                   End If
    
                   ' Now grab the DACL once more...
                   lResult = GetSecurityDescriptorDacl(bSDBuf(0), _
                      lDaclPresent, pAcl, lDaclDefaulted)
    
                   ' A return code of zero means the call failed; test for this
                   ' before continuing.
                   If (lResult = 0) Then
                      MsgBox "Error: " & _
                         "Unable to Get DACL from File Security Descriptor"
                      Exit Sub
                   End If
    
                   ' Once again, grab the ACL for this file.
                   lResult = GetAclInformation(pAcl, sACLInfo, _
                      Len(sACLInfo), 2&)
    
                   ' A return code of zero means the call failed; test for this
                   ' before continuing.
                   If (lResult = 0) Then
                      MsgBox "Error: Unable to Get ACL from File Security " & _
                         "Descriptor"
                      Exit Sub
                   End If
    
                   ' Now show the new ACE count for this file SD!!!
                   MsgBox "ACE Count for this File Is: " & sACLInfo.AceCount
    
                End If
    
             End If
    
          End Sub 
  8. Start NotePad, and create a new text file.


  9. Save the text file as "C:\Readme.Txt," and exit NotePad.


  10. In the Microsoft Visual Basic IDE, press the F5 key to run the project.


  11. Click the "Command1" button on the running form. This calls the routine that will add an ACE to the newly-created text file.


  12. Each time you click the "Command1" button, another ACE will be added, and the project will tell you how many ACEs there are for that file.



REFERENCES

For additional information, please see the following article in the Microsoft Knowledge Base:

Q102102 HOWTO: Add an Access-Allowed ACE to a File
The above article gives more detailed information on the steps that are taken in this example. This article simply shows how to accomplish the same thing from Visual Basic, where many of the above-mentioned API calls are very difficult to call successfully.

Explore the Microsoft Developers Network for detailed information for all APIs, Structures, and Constants used in this example.

Additional query words:


Keywords          : kbAccess kbAPI kbNTOS kbVBp kbVBp500 kbVBp600 
Version           : WINDOWS:5.0,6.0
Platform          : WINDOWS 
Issue type        : kbhowto 

Last Reviewed: July 13, 1999