HOWTO: Set Security on a NTFS Folder Programmatically

ID: Q240176


The information in this article applies to:
  • Microsoft Visual Basic Learning, Professional, and Enterprise Editions for Windows, versions 5.0, 6.0
    on the following platforms: NT


SUMMARY

This article describes how to set security on a folder using security APIs from Visual Basic. The folder needs to be created on an NTFS partition and you need to be a member of the Administrators group. You also need to have read/write permission (READ_CONTRIOL and WRITE_DAC).


MORE INFORMATION

All objects in Microsoft Windows NT have security attributes that are described by a Security Descriptor. The Security Descriptor contains information about who owns the object and who has access to the object. The Security Descriptor contains an Access Control List (ACL) specifying the permissions for users and groups on the object. There are two types of ACLs: discretionary and system. The discretionary ACL (DACL) is controlled by the owner of the object. The DACL contains an entry for each user, global group, or local group given access permission to the object. Each of these entries in the list has an Access Control Entry (ACE). An ACE contains an ACE_HEADER structure, along with the access permission for that ACE type and the Security Identifier (SID). The ACE_HEADER defines the type of ACE (ACCESS_ALLOWED_ACE_TYPE or ACCESS_DENIED_ACE_TYPE), the size of the ACE, and the control flags for the ACE. The access permission determine the type of permission (that is, read, write, and so on) that the user or group has. The process below describes how to modify the DACL for a directory. This requires adding two ACEs. One ACE for the directory itself and any subdirectories and another ACE for any files in the directory.

Note

  • The following code changes permissions on a folder to Add & Read or Change.


  • The folder needs to be created on an NTFS partition.


  • You need to be an Administrator on the machine in question and have read/write (READ_CONTROL and WRITE_DAC) access to the file or directory.


Step to Reproduce Behavior

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


  2. Add a Textbox (Text1) and two CommandButtons (Command1 and Command2) to Form1.


  3. Add the following code to the General Declarations of Form1.


  4. 
    Option Explicit
    
    Private Type SID_IDENTIFIER_AUTHORITY
          Value(5) As Byte '6 bytes
    End Type
    
    Private Type ACE_HEADER
           AceType As Byte
           AceFlags As Byte
           AceSize As Integer
    End Type
    
    Private Type ACCESS_ALLOWED_ACE
           Header As ACE_HEADER
           Mask As Long
           SidStart As Long
    End Type
    
    Private Type ACL
           AclRevision As Byte
           Sbz1 As Byte
           ACLSize As Integer
           AceCount As Integer
           Sbz2 As Integer
    End Type
    
    Private Type SECURITY_DESCRIPTOR
           Revision As Byte
           Sbz1 As Byte
           Control As Integer
           Owner As Long
           Group As Long
           sacl As ACL
           dacl As ACL
    End Type
    
    Private Type SECURITY_ATTRIBUTES
           nLength As Long
           lpSecurityDescriptor As Long
           bInheritHandle As Long
    End Type
    
    Private Declare Function InitializeSecurityDescriptor Lib "advapi32.dll" _
      (ByVal pSecurityDescriptor As Long, ByVal dwRevision As Long) As Long
    
    Private Declare Function AllocateAndInitializeSid Lib "advapi32.dll" ( _
      pIdentifierAuthority As SID_IDENTIFIER_AUTHORITY, _
      ByVal nSubAuthorityCount As Byte, _
      ByVal nSubAuthority0 As Long, _
      ByVal nSubAuthority1 As Long, _
      ByVal nSubAuthority2 As Long, _
      ByVal nSubAuthority3 As Long, _
      ByVal nSubAuthority4 As Long, _
      ByVal nSubAuthority5 As Long, _
      ByVal nSubAuthority6 As Long, _
      ByVal nSubAuthority7 As Long, _
      pSid As Long) _
    As Long  ' pSid above in AllocateAndInitializeSid: pass pointer byref
    ' pSid in GetLengthSid below is dereferenced pass byval
    Private Declare Function GetLengthSid Lib "advapi32.dll" _
         (ByVal pSid As Long) As Long
    ' pSid is dereferenced in FreeSid pass byval
    Private Declare Sub FreeSid Lib "advapi32.dll" (ByVal pSid As Long)
    ' pSid  is dereferenced CopySid pass byval
    Private Declare Function CopySid Lib "advapi32.dll" _
          (ByVal nDestinationSidLength As Long, _
          pDestinationSid As Byte, ByVal pSid As Long) As Long
    Private Declare Function InitializeAcl Lib "advapi32.dll" (pacl As Byte, _
          ByVal nAclLength As Long, ByVal dwAclRevision As Long) As Long
    Private Declare Function SetSecurityDescriptorDacl Lib "advapi32.dll" _
          (ByVal pSecurityDescriptor As Long, ByVal bDaclPresent As Long, _
          pDacl As Byte, ByVal bDaclDefaulted As Long) As Long
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
          (Destination As Any, Source As Any, ByVal Length As Long)
    Private Declare Function AddAce Lib "advapi32.dll" (pacl As Byte, _
         ByVal dwAceRevision As Long, ByVal dwStartingAceIndex As Long, _
           pAceList As Byte, ByVal nAceListLength As Long) As Long
    Private Declare Function SetFileSecurity Lib "advapi32.dll" _
          Alias "SetFileSecurityA" (ByVal lpFileName As String, _
          ByVal SecurityInformation As Long, ByVal pSecurityDescriptor As Long) _
          As Long
    
    Private Const SECURITY_DESCRIPTOR_REVISION = (1)
    Private Const ACL_REVISION = 2
    Private Const GENERIC_READ = &H80000000 ' Read only
    Private Const GENERIC_ALL = &H10000000 ' Full Control
    Private Const SECURITY_BUILTIN_DOMAIN_RID = &H20
    Private Const DOMAIN_ALIAS_RID_ADMINS = &H220
    Private Const INHERIT_ONLY_ACE = &H8
    Private Const OBJECT_INHERIT_ACE = &H1
    Private Const GENERIC_EXECUTE = &H20000000
    Private Const MAXDWORD = &HFFFFFFFF
    Private Const CONTAINER_INHERIT_ACE = &H2
    Private Const DACL_SECURITY_INFORMATION = &H4&
    Private Const ACCESS_ALLOWED_ACE_TYPE = &H0&
    Private Const READ_CONTROL = &H20000
    Private Const SYNCHRONIZE = &H100000
    Private Const DELETE = &H10000
    Private Const GENERIC_WRITE = &H40000000
    
    Private Function ChangePermission(sFName As String, FirstAceMask As Long, SecondAceMask As Long) As Boolean
       
       ' This function will set permissions for Folder specified in sFName
       ' FirstAceMask will set the mask for the files in the folder
       ' SecondAceMask will set the mask for the folder and subfolders.
       
       Dim udtSidIdentifierAuthority As SID_IDENTIFIER_AUTHORITY
       Dim udtAccessAllowedAce As ACCESS_ALLOWED_ACE
       Dim pSid As Long, ACLSize As Long
       Dim lAceSize As Long
       Dim x As Long
       Dim i As Integer
          
       ' To assign permissions you need to be a member of the
       ' Administrators group and the folder must be on an NTFS partition.
       For i = 0 To 4
           udtSidIdentifierAuthority.Value(i) = 0
       Next i
       udtSidIdentifierAuthority.Value(5) = 5  ' SECURITY_NT_AUTHORITY
          
       Dim psdl As Long ' used to get security descriptor pointer
       
       ' Initialize Security Descriptor - the first paramter is address of the
       ' security descriptor.
       x = InitializeSecurityDescriptor(VarPtr(psdl), _
           SECURITY_DESCRIPTOR_REVISION)
       If x = 0 Then
           MsgBox "InitializeSecurityDescriptor failed"
           Exit Function
       End If
       
       ' Allocate and initialize a Sid; Administrative group
       ' The first parameter is the Sid Authority Identifier which identifies
       '    the top level authority which is the administrators group.
       ' The second parameter indicates that there are 2 subauthorities to
       '    be placed in the SID.
       ' The two subauthorities SECURITY_BUILTIN_DOMAIN_RID and
       '    DOMAIN_ALIAS_RID_ADMINS are RID identifiers for the
       '    Administrators group.
       ' The RID is the Relative Identifier found in the SID that identifies
       '    the user or group.  In this case it identifies the Administrators
       '    group.
       ' The combinded SID and RIDs identify the Administrators group.
       ' The last paramter, pSid is a pointer to a pointer to the sid.
       '    It is passed byref here and later dereferenced passing byval.
       '    It is passed byval in GetLengthSid, CopySid and FreeSid.
       x = AllocateAndInitializeSid(udtSidIdentifierAuthority, 2, _
                SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, _
                0, 0, 0, 0, 0, 0, pSid)
       If x = 0 Then
           MsgBox "AllocateAndInitializeSid failed"
           Exit Function
       End If
       
       ' Calculate length of ACL
       Dim AnAcl As ACL, AnAAA As ACCESS_ALLOWED_ACE
       
       ACLSize = Len(AnAcl) + Len(AnAAA) - Len(x) + GetLengthSid(pSid) + _
               Len(AnAAA) - Len(x) + GetLengthSid(pSid)
        
       ' Allocate memory for ACL
       ReDim bufACL(ACLSize - 1) As Byte
       
       ' Init the ACL
       ' Creates a new ACL structure.  This is a variable length structure.
       ' A byte array is created to hold the contiguous bytes.  The size of
       ' this buffer was calculated above in ACLSize.  In this call the pointer
       ' to the first element of the array is passed.
       ' The third parameter must be ACL_REVISION.
       x = InitializeAcl(bufACL(0), ACLSize, ACL_REVISION)
       If x = 0 Then
           MsgBox "InitializeAcl failure"
           Exit Function
       End If
         
       ' Set values in AllowedAccessAce structure:
       ' It is easier to assign values using UDT notation.
       ' There are 2 types of ACEs.  Access allowed and Access denied.
       ' This field indicates that we are using an Access allowed ACE.
       udtAccessAllowedAce.Header.AceType = ACCESS_ALLOWED_ACE_TYPE
       ' AceFlags field specifies control flags.
       ' INHERIT_ONLY_ACE does not apply to containers (folder) but to objects
       ' in the container (files).
       ' OBJECT_INHERIT_ACE indicates that the ACE is inherited by
       ' non container objects such as files within the container object
       ' to which the ACE is assigned.
       udtAccessAllowedAce.Header.AceFlags = _
           INHERIT_ONLY_ACE Or OBJECT_INHERIT_ACE
       ' Size of the ACE
       lAceSize = Len(AnAAA) - Len(x) + GetLengthSid(pSid)
       udtAccessAllowedAce.Header.AceSize = lAceSize
       ' The Mask specifies access rights granted to the ACE.
       ' See SDK documentation under ACCESS_MASK for the break down by bits.
       udtAccessAllowedAce.Mask = FirstAceMask
       ReDim bufAce(lAceSize - 1) As Byte
       ' Copy the AccessAllowedAce structure(UDT) to buffer.
       CopyMemory bufAce(0), udtAccessAllowedAce, lAceSize
       ' Copy sid to buffer where bufAce is the destination buffer -
       ' pSid is ptr to source SID.
       ' y(8) corresponds to SidStart field in AccessAllowedAce struct.
       x = CopySid(GetLengthSid(pSid), bufAce(8), pSid)
        If x = 0 Then
           MsgBox "CopySid fail " & Err.LastDllError
           Exit Function
        End If
           
        ' Add an ACE to ACL.  This is done twice.
        ' This first AddAce call applies to files in the folder.
        ' The first parameter is a pointer to the variable length ACL which is
        '     passed using a pointer to the first element in a byte array.
        ' The second parameter needs to be ACL_REVISION.
        ' The third paramter specifies the position of the ACE in the
        '    ACL which in this case is at the end.
        ' The fourth parameter is a pointer to one or more ACEs.
        '    These ACEs would be placed in contiguous memory and are
        '    placed in a byte array the size
        ' which is placed in the last parameter.
        x = AddAce(bufACL(0), ACL_REVISION, MAXDWORD, bufAce(0), _
            udtAccessAllowedAce.Header.AceSize)
        If x = 0 Then
            MsgBox "First AddAce failed " & Err.LastDllError
        End If
        
        CopyMemory udtAccessAllowedAce, bufAce(0), _
            udtAccessAllowedAce.Header.AceSize
            
        udtAccessAllowedAce.Mask = SecondAceMask
        udtAccessAllowedAce.Header.AceFlags = CONTAINER_INHERIT_ACE
    
        CopyMemory bufAce(0), udtAccessAllowedAce, _
            udtAccessAllowedAce.Header.AceSize
       
        
        ' bufACL(0) - ptr to first element in byte array that
        ' contains contents of the acl structure - ACE gets added to this ACL
        ' which contains ACEs stored contiguously.
        ' This ACE applies to directories and subdirectories.
            
        x = AddAce(bufACL(0), ACL_REVISION, MAXDWORD, bufAce(0), _
            udtAccessAllowedAce.Header.AceSize)
        If x = 0 Then
            MsgBox "Second AddAce failed " & Err.LastDllError
        End If
            
        ' Set the DACL in the security descriptor
        ' The first paramter is the pointer to the security descriptor.
        ' The second paramter is boolean indicating the presence of a DACL
        ' in the Security Descriptor.
        ' The third parameter is the address of the DACL which is variable
        ' length and passed in a byte array.
        ' The fourth paramter indicates that DACL is created by user.
        x = SetSecurityDescriptorDacl(VarPtr(psdl), 1, bufACL(0), 0)
        If x = 0 Then
           MsgBox "SetSecurityDescriptorDacl failure " & Err.LastDllError
           Exit Function
        End If
        
        Dim si As Long
        si = DACL_SECURITY_INFORMATION
       
        ' Set security on Folder object
        x = SetFileSecurity(sFName, si, VarPtr(psdl))
        If x = 0 Then
            MsgBox "SetFileSecurity failure " & Err.LastDllError
            Exit Function
        End If
        
        ' Free Sid
        FreeSid pSid
        
        ChangePermission = True
        
    End Function
    
    Private Sub Command1_Click()
    
        Dim rslt As Boolean
        Dim FirstAddReadMask As Long, SecondAddReadMask As Long
        ' Add & Read
        FirstAddReadMask = GENERIC_EXECUTE Or GENERIC_READ
        SecondAddReadMask = READ_CONTROL Or SYNCHRONIZE Or &H1BF
                            ' (&h01bf standard rights for folder)
        rslt = ChangePermission(Text1.Text, FirstAddReadMask, _
            SecondAddReadMask)
        If Not rslt Then
            MsgBox "ChangePermission failed"
        End If
        
    End Sub
    
    Private Sub Command2_Click()
    
        Dim rslt As Boolean, FirstChangeMask As Long, SecondChangeMask As Long
        ' Change
        FirstChangeMask = GENERIC_READ Or GENERIC_EXECUTE Or _
            DELETE Or GENERIC_WRITE
        SecondChangeMask = READ_CONTROL Or DELETE Or SYNCHRONIZE Or &H1BF
                                     ' (&h01bf standard rights for folder)
        rslt = ChangePermission(Text1.Text, FirstChangeMask, SecondChangeMask)
        If Not rslt Then
            MsgBox "ChangePermission failed"
        End If
        
    End Sub
    
    Private Sub Form_Load()
        Form1.Caption = "Enter folder - click btn to change permissions"
        Command1.Caption = "Add && Read permissions"
        Command2.Caption = "Change permissions"
        Text1.Text = "d:\test"
    End Sub
     
  5. Run the application.


  6. In the TextBox, enter the name of the folder you want to change permissions on. (D:\test is entered by default.)


  7. Click the Add & Read permissions button to give Add & Read permissions to the folder or click the Change Permissions button to give Change permssions to the folder.


  8. To check the permissions on the folder, right-click Explorer. Select the Properties menu item and click on the Security Tab of the Properties dialog. On the Security tab, click the Permissions button. The specific account should say Add & Read or Change depending on which button you clicked in the above sample.


Again, the folder needs to be created on an NTFS partition and you need to be an Administrator on the machine in question and have read/write (READ_CONTROL and WRITE_DAC) access to the file or directory.


REFERENCES

For additional information on using NT Security through code, click the article number below to view the article in the Microsoft Knowledge Base:

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

Additional query words:

Keywords : kbAPI kbNTOS400 kbSDKWin32 kbSecurity kbVBp kbVBp500 kbVBp600 kbGrpVB kbDSupport
Version : WINDOWS:5.0,6.0
Platform : WINDOWS
Issue type : kbhowto


Last Reviewed: December 13, 1999
© 2000 Microsoft Corporation. All rights reserved. Terms of Use.