Windows PowerShell Tip of the Week

Here’s a quick tip on working with Windows PowerShell. These are published every week for as long as we can come up with new tips. If you have a tip you’d like us to share or a question about how to do something, let us know.

Find more tips in the Windows PowerShell Tip of the Week archive.

Working with Security Descriptors

For many people, security descriptors have always represented the Holy Grail of system administration scripting: shrouded in mystery and mythology, security descriptors are the one thing that everyone dreams about yet never expects to see for themselves.

That’s interesting, too, because it’s definitely not impossible to manage security descriptors using scripts. After all, WMI has had this capability for years, and in its very first release Windows PowerShell introduced a cmdlet named Set-ACL. So what’s the big deal?

Well, the big deal is the fact that something that might be theoretically possible isn’t always very practical. Can you manage security descriptors using WMI? Sure, although it’s definitely not for the faint-of-heart. OK, but can’t you configure a security descriptor using Set-ACL? Sure, but, by default, that’s only if you manually configure the security settings on another object (e.g., a file) and then copy those settings to a new object. Here’s an example taken from the PowerShell help file:

$DogACL = get-acl c:\dog.txt
set-acl -path C:\cat.txt -AclObject $DogACL

You get the security descriptor off the file Dog.txt and then copy that security descriptor to Cat.txt. That’s useful, but isn’t quite what most people have in mind.

So isn’t there any way you can easily configure security descriptors using a script? Well, as it turns out, there is, as long as you’re willing to use Windows PowerShell, and as long as you’re willing to dive headfirst into the .NET Framework:

$colRights = [System.Security.AccessControl.FileSystemRights]"Read, Write"

$InheritanceFlag = [System.Security.AccessControl.InheritanceFlags]::None
$PropagationFlag = [System.Security.AccessControl.PropagationFlags]::None

$objType =[System.Security.AccessControl.AccessControlType]::Allow

$objUser = New-Object System.Security.Principal.NTAccount("wingroup\kenmyer")

$objACE = New-Object System.Security.AccessControl.FileSystemAccessRule `
    ($objUser, $colRights, $InheritanceFlag, $PropagationFlag, $objType)

$objACL = Get-ACL "C:\Scripts\Test.ps1"
$objACL.AddAccessRule($objACE)

Set-ACL "C:\Scripts\Test.ps1" $objACL

Granted, at first glance this might not look like a solution that lets you easily configure security descriptors from a script. But bear with us; you might change your mind about that, especially after we explain how the script works, and after we point out that much of the script is boilerplate text that can be used in any PowerShell script you use to manage security descriptors.

And, then again, you might not change your mind. But we’ll soon find out, won’t we?

Let’s begin at the beginning – no, wait, let’s begin even before the beginning. Before we go any further we should note that the Scripting Guys have had only a limited amount of time to play around with security descriptors on files and folders. Can we guarantee that this sample script will work under any and all conditions? No. Can we guarantee that this script will not mess up the security on a given file or folder? No. Can we guarantee that this script will not tear a hole in the space-time continuum and cause you to be sucked into a black hole? No. The sample script we showed you works just fine for us, but that’s all we can say. Use this sample at your own risk, and use it wisely (i.e., use it on a test machine until you’re satisfied it won’t cause you any problems).

That should set your mind at ease, huh?

OK, now let’s begin at the beginning. In our first line of code, we’re creating an array of file system rights; that’s what the construction [System.Security.AccessControl.FileSystemRights] (a .NET Framework file system enumeration) represents. In our sample script, we’re assigning two rights (Read and Write) to a variable named $colRights; we’ll use this variable later on, when we actually create our new ACE (access control entry):

$colRights = [System.Security.AccessControl.FileSystemRights]"Read, Write"

Are Read and Write the only two file system rights than can be assigned in a script? No. In theory (we say “in theory” because we haven’t tried all the various possibilities) you can assign any of the following rights:

  • AppendData

  • ChangePermissions

  • CreateDirectories

  • CreateFiles

  • Delete

  • DeleteSubdirectoriesAndFiles

  • ExecuteFile

  • FullControl

  • ListDirectory

  • Modify

  • Read

  • ReadAndExecute

  • ReadAttributes

  • ReadData

  • ReadExtendedAttributes

  • ReadPermissions

  • Synchronize

  • TakeOwnership

  • Traverse

  • Write

  • WriteAttributes

  • WriteData

  • WriteExtendedAttributes

Got that? To make a long story short, in the first line of code we simply indicate the file system rights we’re going to assign.

In the next two lines of code we indicate how these rights should be inherited and propagated to child items. (This is really aimed more at folders than at files.) In both cases we set the value to None. Alternatively, we could have set the InheritanceFlag to any of the following:

  • ContainerInherit (the ACE is inherited by child containers, like subfolders)

  • ObjectInherit (the ACE is inherited by child objects, like files)

  • None

Along the same lines, we could have set the PropagtionFlag to any of these options:

  • InheritOnly (the ACE is propagated to all child objects)

  • NoPropagateInherit (the ACE is not propagated to child objects)

  • None

How are you supposed to know what all these values mean? Our advice is to set up a test folder, throw some test files into that folder, then try various combinations of InheritFlags and PropagateFlags and see what happens.

Next we declare the ACE type; an ACE can either Allow access to an object or Deny access to an object. In our case, we’re going to allow access:

$objType =[System.Security.AccessControl.AccessControlType]::Allow

See? That’s no so bad, is it? Neither is the next line of code, which simply creates a new object ($objUser) representing the user to be assigned these rights:

$objUser = New-Object System.Security.Principal.NTAccount("wingroup\kenmyer")

At this point in time we’re ready to create our new ACE in memory. That’s something we do by creating a new instance of the System.Security.AccessControl.FileSystemAccessRule class, taking care to pass all our new variables (like $objUser and $colRights) as parameters to the New-Object cmdlet:

$objACE = New-Object System.Security.AccessControl.FileSystemAccessRule `
    ($objUser, $colRights, $InheritanceFlag, $PropagationFlag, $objType)

This, of course, is all boilerplate; as long as you’ve assigned the correct values to the individual variables then this line of code should work in any security descriptor script as-is.

But wait, don’t go; we aren’t done yet. All we’ve done up to this point is create a new ACE in memory; we haven’t actually added this new ACE to a file or folder. In order to do that, we first need to retrieve the security descriptor from that file or folder; that’s what this line of code is for:

$objACL = Get-ACL "C:\Scripts\Test.ps1"

You’re right: this was an easy one,wasn’t it? All we’re doing is using the Get-ACL cmdlet to retrieve the security descriptor from the file C:\Scripts\Test.ps1, then storing that data in a variable named $objACL.

As soon as we have an object reference to the security descriptor for Test.ps1 we can then use the AddAccessRule method to add our new ACE:

$objACL.AddAccessRule($objACE)

All that’s left now is to use the Set-ACL cmdlet to assign the modified security descriptor back to Test.ps1:

Set-ACL "C:\Scripts\Test.ps1" $objACL

So is that really all there is to it? Well, let’s find out. Try running this command to retrieve the security descriptor and display it as a list:

Get-ACL "C:\Scripts\Test.ps1" | Format-List

Now let’s see if the user fabrikam\kenmyer appears anywhere in the security descriptor:

Path   : Microsoft.PowerShell.Core\FileSystem::C:\scripts\test.ps1
Owner  : FABRIKAM\pilarackerman
Group  : FABRIKAM\Domain Users
Access : FABRIKAM\kenmyer Allow  Write, Read, Synchronize
         BUILTIN\Administrators Allow  FullControl
         NT AUTHORITY\SYSTEM Allow  FullControl
         FABRIKAM\ pilarackerman Allow  FullControl
         BUILTIN\Users Allow  ReadAndExecute, Synchronize

Well, what do you know:

Access : FABRIKAM\kenmyer Allow  Write, Read, Synchronize

Like we said, this seems to work just fine. Still, and at the risk of repeating ourselves, it might be a good idea to play around with it on a test computer before you start messing with security descriptors on a real computer.

Now, what happens if you change your mind and decide to get rid of an ACE? Well, there are two ways to go about doing that. To begin with, you can use the method RemoveAccessRule to remove a single right. For example, this script removes Write from the kenmyer ACE:

$colRights = [System.Security.AccessControl.FileSystemRights]"Write"

$InheritanceFlag = [System.Security.AccessControl.InheritanceFlags]::None
$PropagationFlag = [System.Security.AccessControl.PropagationFlags]::None

$objType =[System.Security.AccessControl.AccessControlType]::Allow

$objUser = New-Object System.Security.Principal.NTAccount("wingroup\kenmyer")

$objACE = New-Object System.Security.AccessControl.FileSystemAccessRule `
    ($objUser, $colRights, $InheritanceFlag, $PropagationFlag, $objType)

$objACL = Get-ACL "c:\scripts\test.ps1"
$objACL.RemoveAccessRule($objACE)

Set-ACL "C:\Scripts\Test.ps1" $objACL

As you can see, the approach is almost identical. In fact, there are only two differences. First, we only assigned Write to our list of file system rights; that’s because this is the only right we want to deal with:

$colRights = [System.Security.AccessControl.FileSystemRights]"Write"

Second, we used the RemoveAccessRule method to remove this right from the security descriptor:

$objACL.RemoveAccessRule($objACE)

Alternatively, we can use the RemoveAccessRuleAll method to completely remove the user fabrikam\kenmyer from the security descriptor:

$colRights = [System.Security.AccessControl.FileSystemRights]"Read"

$InheritanceFlag = [System.Security.AccessControl.InheritanceFlags]::None
$PropagationFlag = [System.Security.AccessControl.PropagationFlags]::None

$objType =[System.Security.AccessControl.AccessControlType]::Allow

$objUser = New-Object System.Security.Principal.NTAccount("wingroup\kenmyer")

$objACE = New-Object System.Security.AccessControl.FileSystemAccessRule `
    ($objUser, $colRights, $InheritanceFlag, $PropagationFlag, $objType)

$objACL = Get-ACL "c:\scripts\test.ps1"
$objACL.RemoveAccessRuleAll($objACE)

Set-ACL "C:\Scripts\Test.ps1" $objACL

This time around we add just a single right – Read – to our collection of file system rights. As near as we can tell you have to have something listed as a file system right, although it doesn’t seem to matter what. And it doesn’t matter if the ACE encompasses a whole bunch of rights; just adding one right to the collection appears to do the trick.

In addition, we also use a different method in this script, RemoveAccessRuleAll:

$objACL.RemoveAccessRuleAll($objACE)

What’s this method going to do? You got it: it’s going to remove the entire ACE. Run Get-ACL again and take a look at the security descriptor for Test.ps1:

Path   : Microsoft.PowerShell.Core\FileSystem::C:\scripts\test.ps1
Owner  : FABRIKAM\pilarackerman
Group  : FABRIKAM\Domain Users
Access : BUILTIN\Administrators Allow  FullControl
         NT AUTHORITY\SYSTEM Allow  FullControl
         FABRIKAM\ pilarackerman Allow  FullControl
         BUILTIN\Users Allow  ReadAndExecute, Synchronize

Not bad, not bad at all.

Admittedly that’s a somewhat cursory introduction to working with security descriptors, but it’s all we have time for this week. Give it a try (on a – that’s right, on a test machine); meanwhile, the Scripting Guys will continue to play with this as well. Between the two of us, we should eventually find the Holy Grail of system administration scripting: a way to programmatically manage security descriptors.

See you next week.