Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Setting File or Directory permissions ACE entries with .NET using CACLS.EXE


:P
On this page:

Did you ever need to set an ACE (Access Control Entry) on a directory or a file? I’ve had plenty of applications in the past where I needed to set permissions on a directory or file. Most of these were application configuration applications. Usually this is required for automatically installing Web applications that get redistributed in some way.


I recently was working on a generic Web Configuration utility, that performs setup tasks for Web applications in a semi-generic way so that you can ship a generic install utility that can configure your Web application. One of the important features of this tool is the ability to set permissions on a folder.

 

Assigning permissions to a file or directory is one of those black art topics when it comes to doing it via code. I remember looking for examples in C++ for this a few years back as part of the West Wind Web Connection installation. At the time I gave up on it because it took a ton of really ugly code that I couldn’t figure out .

 

That was then, this is now and we’re still not a whole lot better off - .NET also doesn’t include ACE/L support (although .NET 2.0 is rumored to have a class to allow setting of directory permissions).

 

Regardless, it’s possible to fairly easily integrate this functionality by using the CACLS.EXE command line utility which is included in all versions of WinNT forward through XP and Win2003 Server. CACLS allows you to create individual ACE entries. With CACLS it's pretty easy to create ACE entries easily.

 

The following class encapsulates the basics of creating a single ACE entry.

 

///

/// This class sets an individual ACL value on a file or directory.

///

public class DirectoryPermission

{

      public DirectoryPermission()

      {

      }

 

      public DirectoryPermission(string Path, string Username, string UserRights)

      {

            this.Pathname = Path;

            this.Username = Username;

            this.UserRights = UserRights;

      }

 

      ///

      /// Directory Resource to assign ACL to. This is a physical disk path

      /// like d:\temp\test.

      ///

      public string Pathname = "";

 

      ///

      /// Username or group name to assign to this resource

      ///

      public string Username = "";

     

      ///

      /// Rights to assign for the given user or group.

      /// N - None

      /// R - Read

      /// C - Change

      /// F - Full

      ///

      public string UserRights = "R";

 

      ///

      /// Determines whether the permissions walk down all child

      /// directories.

      ///

      public bool InheritSubDirectories = true;

 

      ///

      /// When set overrides the existing ACLs and only attaches this

      /// ACL effectively deleting all other ACLs. Generally you won't

      /// want to do this and only change/add the current ACL.

      ///

      public bool OverrideExistingRights = false;

 

      ///

      /// Error Message set by SetAcl

      ///

      [XmlIgnore]

      public string ErrorMessage = "";

 

      ///

      /// Sets the actual ACL based on the property settings of this class

      ///

      ///

      public bool SetAce()

      {

            if ( this.Pathname == null || this.Pathname == "")

            {

                  ErrorMessage +=  "Path cannot be empty.";

                  return false;

            }

 

            string CommandLine = '"' + System.IO.Path.GetFullPath(this.Pathname) + '"' +

                                 " /C ";

 

            if (this.InheritSubDirectories)

                  CommandLine += @" /T ";

            if (!this.OverrideExistingRights)

                  CommandLine += @" /E ";

 

            CommandLine += @" /P " + this.Username + ":" + this.UserRights;

 

            Process p = new Process();

            p.StartInfo.FileName = "cacls.exe";

            p.StartInfo.Arguments = CommandLine;

            p.StartInfo.RedirectStandardOutput = true;

            p.StartInfo.UseShellExecute = false;

            p.StartInfo.WindowStyle = ProcessWindowStyle.Minimized ;

            p.StartInfo.CreateNoWindow = true;

 

            p.Start();

 

            string Response = p.StandardOutput.ReadToEnd();

 

            // *** If no output gets generated there's an error

            if (Response == null || Response == "")

            {

                  this.ErrorMessage += "Unable to set permissions on " +

                                       this.Pathname + " for " + this.Username ;

                  return false;

            }

 

            return true;

      }

}

 

 

This class works by simply using the Process class to execute the CACLS utility. It maps the various property settings to command line options and then simply executes the command.

 

You need to be an administrator in order to be able to set permissions on directories and to be able to execute external programs out of the application.

 

The routine redirects StandardOutput to an internal stream that’s checked for a result. If the result is empty the CACLS call failed. Otherwise the call worked and the routine returns .t.

 

The command line looks something like this to create a single ACE entry:

 

CACLS.EXE "d:\MyApp\WebPath" /T /P IUSR_RASNOTEBOOK:R

 

The /T switch specifies that subdirectories will inherit the settings. /P plus the user or group name followed by colon and one of the following first letters: Read, Change, Write, Full.

 

This is sort of a brute force way of performing this task but it works well and the class encapsulates the process in such a way that it’s non-intrusive for the calling application. The Console window that CACLS runs in is minimized so it doesn’t affect the desktop other than a quick flash in the taskbar. 

 

How would you use this? In the Web Configuration utility the code figures out the Anonymous Web User by querying IIS through ADSI, and adding this user to the application directory with Read attributes. It also checks for the IIS version and if it's IIS 5 add ASPNET and with IIS 6 NETWORKSERVICE and adds it with Full rights for the directory (this app needs to read the web.config file as well as write a couple of XML files). These rights are automatically added to a listbox and then shown in the UI. The actual routines then pick up the values in the list box and assign it to Configuration object which contains an ArrayList of DirectoryPermission objects. The code then simply loops through the list and calls the SetAce method to set the rights.

 

The assignment logic looks like this:

 

public bool AddDefaultWebPermissions()

{

      if (this.Virtual.Virtual == null || this.Virtual.Virtual == "")

            return true;

 

      VirtualDirectory vd = new VirtualDirectory();

 

      vd.IISPath = this.Virtual.IISPath;

      vd.Virtual = this.Virtual.Virtual;

 

      if (!vd.GetVirtual() )

      {

            if (! vd.GetVirtual("") )

            {

                  this.SetError(vd.ErrorMessage);

                  return false;

            }

      }

 

      // *** Add the Anonymous User

      string AnonymousUser = vd.AnonymousUserName;

      this.DirectoryPermissions.Add( new DirectoryPermission( this.Virtual.Path,AnonymousUser,"R") );

 

      if (vd.ServerType == WebServerTypes.IIS6)

      {

            this.DirectoryPermissions.Add( new DirectoryPermission( this.Virtual.Path,"NETWORKSERVICE","F") );

      }

      else

      {

            this.DirectoryPermissions.Add( new DirectoryPermission( this.Virtual.Path,"ASPNET","F") );

      }

 

      return true;

}

 

and to run the actual permissions:


 

public bool SetDirectoryPermissions()

{

      bool Error = false;

 

      // *** Create Directory Permissions

      foreach( DirectoryPermission dm in this.DirectoryPermissions )

      {

            if (!dm.SetAce() ) 

            {

                  this.SetError( dm.ErrorMessage );

                  Error = true;

            }

      }

 

      return !Error;

}

 

Setting directory permissions isn't something you do every day, but when you need it it usually is critical to avoid manual configuration.

 

You can apply this same logic to other languages easily enough. For example, here's a Visual FoxPro version that is now used as part of the West Wind Web Connection setup:

 

************************************************************************

* wwUtils :: SetACL

****************************************

***  Function:

***    Assume:

***      Pass: lcPath        -   Path or Filename to assign ACE to

***            lcUser        -   The Username to assign

***            lcAccess      -   [N]one, [R]ead, [C]hange, [F]ull

***            llInherit     -   Pass rights down the directory tree

***            llReplace     -   Replace rights on the resource

***                              (deletes all ACL entries except this one)

***    Return:

************************************************************************

FUNCTION SetACL(lcPath,lcUser,lcAccess,llInherit,llReplace)

LOCAL lcCommand, lcFile

 

*** Strip off any trailing backslashes

IF RIGHT(lcPath,1)="\" AND LEN(lcPath) > 1

   lcPath = SUBSTR(lcPath,1,LEN(lcPath)-1)

ENDIF

  

lcCommand = "RUN Cacls.exe " + ShortPath( lcPath)

 

IF llInherit

   lcCommand = lcCommand + " /T "

ENDIF

IF !llReplace

   lcCommand = lcCommand + " /E "

ENDIF

 

lcCommand = lcCommand + " /P " + lcUser + ":" + lcAccess + " > cacls.txt"

&lcCommand

 

lcFile = ""

IF FILE("Cacls.txt")

   lcFile = FILETOSTR("cacls.txt")

   ERASE Cacls.txt

ENDIF

IF EMPTY(lcFile)

   RETURN .F.

ENDIF

 

RETURN .T.

ENDFUNC

*  wwUtils :: SetACL

 

 

Remember, that even though you can do this under full program control, setting permissions on a directory or file is a super high trust operation and as such may affect the system security significantly. It's good form to always let the user know that you are making this sort of change with a dialog. Also, you should only set permissions if absolutely necessary and always try to minimize the paths that are affected by it.

 


The Voices of Reason


 

--sb--
November 06, 2004

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

If WMI is an option, check 'Create Home Directories and Set NTFS Permissions with a Web Script' at http://www.winnetmag.com/WindowsScripting/Articles/ArticleID/22048/pg/1/1.html

Mike
November 07, 2004

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE


Rick Strahl
November 07, 2004

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

I had looked at the GotDotNet Workspace before but the code was very buggy at the time. The nice thing about CACLS.EXE is that it's easy to deal at least for the base scenario outlined here. If more control is needed and you need to deal more concisely with SIDs and complex manipulation of the ACL then a class like that one (there is one on CodeProject too) will likely be a better choice. For the simple tasks though CACLS works great. As to security rights - well you'll need an admin account to do any of this anyway so that shouldn't be much of a consideration.

David
January 15, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

I'm using following VB code and it doesn't work...



Imports System
Imports System.Diagnostics

Public Class FileSecurity

Public Shared Function SetSecurity() As String

Dim _cmdLine As String = "c:\dave /t /P ASPNET:F"
Try
Dim p As Process = New Process
p.StartInfo.FileName = "cacls.exe"
p.StartInfo.Arguments = _cmdLine
p.StartInfo.RedirectStandardOutput = True
p.StartInfo.UseShellExecute = False
p.StartInfo.WindowStyle = Diagnostics.ProcessWindowStyle.Minimized
p.StartInfo.CreateNoWindow = True
p.Start()
'Return p.StandardOutput.ReadToEnd.ToString
Catch ex As Exception
Return ex.Message
End Try

End Function

End Class

Rick Strahl
January 16, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

Yes it works in ASP.NET, but you need to have permissions for it. You need admin rights to do this which probably means you need to set up the page with Impersonation (or set up your NETWORK SERVICE/ASPNET account with Admin rights).

Sean Kinsey
January 27, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

There is one error though, and that is that the cacls utility requires the user to confirm by pressing y.
This can normaly be done by piping y with 'echo y| cacls xx' but this is not something that I have managed to do with the process class.
I am now going to use a bit more time than the 5 minutes I have used so far to find a solution..

Sean Kinsey
January 27, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

I solved this quite quickly by using this:
p.StartInfo.FileName = "cmd.exe"
p.StartInfo.Arguments = "/c echo y| cacls " & folderPath & " /T /G " & userName & ":F"

Terry
February 02, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

Is there an equivalent application to set group right s?

Prasad Somwanshi
March 17, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

I've similar code in my application.
But it seems it doesn't work on european lang. like german on french.

hhe
May 09, 2005

# re: doesn't work when path contain space in CACLS.EXE

Hi,
Do you knlw why cacls doesn't work when path contains a space? I gave the following command in DOS environment:
cacls.exe c:\program files\my company\my product /T /E /G users:C

thanks,
hhe

Bobba Fett
May 09, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

Try quotes around the path.

blom
May 09, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

I'm hitting an odd error:
using EITHER xcacls OR cacls, I'm unable to assign rights to a group..if the group name is more then 14 characters...
In these examples...the process fails..until I rename the group with one less character..it is the ONLY change I make.
I can reproduce this consistently.

Any help is appreciated.
blom

C:\xcacls.exe c:\temp\Generally /G domain\Generally_SList:R /E /Y
No mapping between account names and security IDs was done.

C:\xcacls.exe c:\temp\Generally /G domain\Generally_List:R /E /Y
processed dir: c:\temp\Generally

blom
May 11, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

Never Mind..I figured out the problem..the xcacls, and cacls tools were keying off the "Windows 2000" group name (the truncated version of the full name) ..so until they 'matched'. the tool appeared to fail.
I fixed my problem by altering my script to reference only the truncated version of the name.
Though ya might like to know..
Cheers!

Robert
May 16, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

is it possible to get calcs.exe to set read permissions with out the execute and list folder contents ?

jander
May 24, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE


Dude
June 08, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

cacls only works with 8 character folder names.

i.e.
long file path:
C:\Program Files\TestFolder
short file path:
C:\Progra~1\TestFo~1

vbcode to convert long file name to short:

Private Declare Function GetShortPathName Lib "kernel32.dll" Alias "GetShortPathNameA" _
(ByVal lpszLongPath As String, ByVal lpszShortPath As String, ByVal cchBuffer _
As Integer) As Integer

Private Function ShortPath(ByVal LongPath As String) As String
Dim shrtPath As String = Space(255)
GetShortPathName(LongPath, shrtPath, shrtPath.Length)

Return shrtPath.Substring(0, InStr(shrtPath, Chr(0)) - 1)
End Function

Augustine
September 14, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

Any idea how do i fix the Access is Denied problem?


Public Function SetSecurity() As String

' Dim _cmdLine As String = "c:\dave /t /P ASPNET:F"
Try
Dim p As Process = New Process
p.StartInfo.FileName = "cmd.exe"
p.StartInfo.Arguments = "/c echo y| cacls " & "C:\Test" & " /T /G " & "DOMAIN\5541192" & ":F"
p.StartInfo.RedirectStandardOutput = True
p.StartInfo.UseShellExecute = False
p.StartInfo.WindowStyle = Diagnostics.ProcessWindowStyle.Minimized
p.StartInfo.CreateNoWindow = True
p.Start()
'Return p.StandardOutput.ReadToEnd.ToString
Return "done"

lbxLogging.Items.Add("RP\5541192 added")
Catch ex As Exception
Return ex.Message
lbxLogging.Items.Add(ex.Message)
End Try
Console.ReadLine()
End Function

mona
November 17, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

Realy it's so useful Code but i wanna getting the permission of specified file .i can get is_full_control or not only
what about is_read_only
and is_execute or not
and is_write_only
and is_read_write
Thanks in advance

Rick Strahl
November 17, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

FWIW, in .NET 2.0 there's built in support for ACL/ACE control for both reading and writing.

Take a look here:
http://msdn.microsoft.com/msdnmag/issues/04/11/AccessControlinNET/default.aspx#S8

You can also use CACLS to read existing permissions and parse it out of the result in 1.1.

mona
November 17, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

Many Thanks i parsed the result and it's done
Thanks 4 ur help

Agus
November 24, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

How can I do this with VS 2003 Framework 1.1?

Thanks,
Agus

Paul Noeldner
November 29, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

Nobody should have to resort to 1980's command line shell syntax in 2005. The NET object framework should certainly provide a full set of functions against the major objects in the environment including the file system, from within the primary supported languages via properties and methods. Checking and setting security programmatically should no longer be an 'afterthought' left out of the framework.

Rick Strahl
November 29, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

Paul - It isn't <g> - .NET 2.0 includes classes that allow doing this programmatically. But prior - man using Win32 APIs to do this, or even WMI which does support this - was a royal pain in the ass.


karunakar.gadde
December 18, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

I am not able to supress the "Are you sure Y/N" Option in Cacls. i want to get is cacls command is successfull or not also.

karunakg@mahindrabt.com
December 18, 2005

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

I am not able to supress the "Are you sure Y/N" Option in Cacls. i want to get is cacls command is successfull or not also from my vb.net program.

Rick Strahl's WebLog
January 04, 2006

# Setting ACE/ACL permissions in .NET 2.0

Setting ACE/ACLs in .NET 2.0 got a bit easier with managed wrapper support built into the BCL. But these classes are still pretty complex if you're not intricately familiar with how these security constructs work. It takes a fair amount of code to do this seemingly common and simple task, and the question begs why nobody went through the excercise of creating a more user friendly class front end.

Rick Strahl
January 04, 2006

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

Regarding surpressing the Y/N option in XP: Youc can use write to Standard "Y\r\n" into the stream to force the letters to be sent. This works with Win2003 as well even though it doesn't use those extra characters.

Here's the updated code:

Process p = new Process();
p.StartInfo.FileName = "cacls.exe";
p.StartInfo.Arguments = CommandLine;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
p.StartInfo.CreateNoWindow = true;

p.Start();

p.StandardInput.Write("Y\r\n");
string Response = p.StandardOutput.ReadToEnd();


Mo
January 09, 2006

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

I am unable to use cacls.exe when long file names are used for folder. I tried putting quotes around the path and then call cacls.exe but I am still getting "Unable to set permissions on "C:\My Long File Path" for ASPNET\MACHINE
Please help :)

Mo
January 09, 2006

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

Never mind. I was putting an extra backslash :)

Anil
February 14, 2006

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

Hi,

I am stuck with a problem where the application which we have depeloed require autoupgrade, but when a limited access user have logged in he is not able to do that as write permissions are not allocated to him on Program Files Foler.
Could this code also be used for the same.

I want some utility so that access to that folder is given for some amount of time till autoupgrade complete.

Walt
September 12, 2006

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

Worked like a charm...just wanted to say thanks.

Rick Strahl's Web Log
November 14, 2006

# Setting ACE/ACL permissions in .NET 2.0 - Rick Strahl's Web Log

Setting ACE/ACLs in .NET 2.0 got a bit easier with managed wrapper support built into the BCL. But these classes are still pretty complex if you're not intricately familiar with how these security constructs work. It takes a fair amount of code to do this seemingly common and simple task, and the question begs why nobody went through the excercise of creating a more user friendly class front end.

Jamie's Blog
March 15, 2007

# Jamie&#8217;s Blog &raquo; Blog Archive &raquo; VFP Database Security


granadaCoder
January 13, 2010

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

Great write up! This is very helpful in continuous integration as well.

A couple of "hints":

/E Edit ACL instead of replacing it.
That's a very important argument! I am just pointing it out and bringing attention to it and your code ( !llReplace ) . I accidentally replaced mine instead of updating them, and had to go back and put back the ones I removed. :<


icacls.exe may be the Windows Server 2008 "update". I think 2003 SP2 includes icacls.exe as well.

Thanks for another great write up!

Narendra
February 04, 2010

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

Hi Rick
i followed this process to create full control to a perticular user.
what i observed is
i have a folder path 'E:\testing', initially on rightclick-->properties-->security tab i have 5 user names.
i ran this command
"CACLS.EXE "'E:\testing" /T /P nreddy:F"
then again i opened security tab but there i hav only one user which i mentioned in command(nreddy) with full control

what happen to remaining 4 users?
those 4 users got deleted for this perticular path???

is there any way to give full control to a perticular user without deleting other users permissions?

mr_3ntropy
February 09, 2010

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

Narendra

Yes. You forgot the /E switch.
E for Edit. If you don't use it, that means you want to replace everything.

vijay sharma
January 24, 2011

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

hello sir,
This is vijay from Mumbai,INDIA.This is the nice things that you wrote.Sir ,same thing i was try to run,but its not woking for my web-site.But its working fine for offline.I have a some.dat file & some.EXE.By using the some.EXE,i have to use some.dat.At my local machine its working good,as we want.But when we are going to put the things at server,there is no error at runtime,but the the some.exe is not enable to call the some.dat , as i think.Kindly guide me.Waiting for your response.

Have a nice time & good day ahead.

Thanks with Regards,

Vijay Sharma

leomark
February 01, 2012

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE


Tharindu
March 08, 2012

# re: Setting File or Directory permissions ACE entries with .NET using CACLS.EXE

               string Pathname = folder;
                EventLog.WriteEntry(Pathname);
                string Username = @"BUILTIN\Users";
                string UserRights = "N";
 
                if (Pathname == null || Pathname == "")
                {
                    EventLog.WriteEntry("No File");
                }
 
                string CommandLine = '"' + System.IO.Path.GetFullPath(Pathname) + '"' + " /C ";
                CommandLine += @" /T ";
                CommandLine += @" /P " + Username + ":" + UserRights;
 
                Process p = new Process();
                p.StartInfo.FileName = "cacls.exe";
                p.StartInfo.Arguments = CommandLine;
                p.StartInfo.RedirectStandardOutput = true;
                p.StartInfo.RedirectStandardInput = true;
                p.StartInfo.UseShellExecute = false;
                p.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
                p.StartInfo.CreateNoWindow = true;
 
                p.Start();
                p.StandardInput.Write("Y\r\n"); 
                string Response = p.StandardOutput.ReadToEnd();
                EventLog.WriteEntry(Response);
                if (Response == null || Response == "")
                {
                    EventLog.WriteEntry("Error");
                }

tell me why this not working in windows service?????

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024