Creating Scan Folders With PowerShell

In the process of moving file servers around, I wanted to clean up a share that’s full of scan destinations for individual users.  There were a number of problems with the original setup:

  • Not everyone had a folder to begin with.
  • The names might or might not match the person’s user name in Active Directory.
  • Everyone had access to every scan folder.

Obviously, a bad situation.  While this client isn’t enormous, it’s large enough that I sure didn’t want to do all of this by hand.  Creating the folders is trivial with a script, but I wasn’t sure how to do the permissions, so decided that this was a good time to find out.

I created the scan folder, turned off inheritance and set the default permissions on the folder, specifically this installation needed the following:

  • Domain Admins: Full Control
  • SYSTEM: Full Control
  • CREATOR OWNER: Full Control [Note:  I realize not everyone does this, but I do and that’s not what this post is about]
  • Scan User: Modify
  • User: Modify

The actual user doesn’t have permissions to the root scan folder, but can access it via the direct path.  In other words:  \\Server\Scans is an access denied error but \\Server\Scans\JSmith works.

Here are the first few lines of the script, the part I already knew how to do.  At this particular client, all of the users who have scan profiles are located in a single OU, so the first thing I did was pull all of those users into an array:

Import-Module ActiveDirectory

# The root folder 
$ScanRoot = "D:\Shares\Scans"

# The OU these users are located in:
$OU = "OU=Scan Users,OU=Corp,DC=Contoso,DC=com"

# Get all of the users in the OU
$users = Get-ADUser -Filter * -SearchBase $OU | Select-Object Name, samAccountName

Next, I needed to iterate through each user and check if a folder needs to be created for that user.  If so, create it.  Again, there’s not much to this part. [NOTE: I’m assuming here that the creation succeeds for the sake of code clarity here]


# Iterate through each user and create a folder, if necessary
ForEach ($user in $users)
{
# The full path to the folder
$FolderPath = "$ScanRoot\$($user.SamAccountName)"

# Check if the folder exists
$Exists = Test-Path $FolderPath

if( $Exists -eq $False )
{
Write-Host "Folder $FolderPath does not exist, creating".
New-Item $FolderPath -ItemType Directory
}

When creating this folder, the permissions from its parent came up by default because inheritance is on for the sub-folders.  I needed to accomplish two things:

  • Clear any ACEs that were applied to just that folder
  • Add Modify permissions for that specific user

The first attempt at this used a cmdlet called Get-ACL, which made sense.  However, I ran into problem after problem using this (apparently because it tries to change ownership when applying changes among other things) and switched to a different method.  This is somewhat non-intuitive as there doesn’t seem to be a good way to create an ACL out of nothing.  You need to grab it off of the file/folder and modify it.


$ACL = (Get-Item $FolderPath).GetAccessControl('Access')

To clear the items, I used the following:


$ACL.Access | %{$acl.RemoveAccessRule($_)} | Out-Null

RemoveAccessRule needs to take an ACE as its only parameter.  That actually seemed a little wonky at first, but it makes sense that you couldn’t just say: Remove all ACEs with Administrator in them.  Well, I suppose you could, but there’s not a function for that.  I pulled the list of all of the ACE’s and removed them. The Out-Null just suppresses command output for cosmetic reasons, no actual function.

With a blank ACL, the next step was to add the permission for the individual user.  This took some browsing around TechNet and some time on Microsoft’s forums as well, but I eventually worked out the following:


# Add the user
$ACE = New-Object  System.Security.AccessControl.FileSystemAccessRule("$($user.samAccountName)","Modify","ContainerInherit,ObjectInherit","None","Allow")
$ACL.SetAccessRule($ACE)

The line that creates the ACE doesn’t make a lot of sense without explaining the parameters, so let’s do that.  I’m using this constructor

  • $($user.samAccountName): This is simply the username from the AD user object at the top of this script
  • Modify: This is the permission level that the user needs to have.  A full list is available on TechNet
  • ContainerInherit,ObjectInherit:  These are flags that set the “This folder, sub-folders and files” option that you see in the Advanced security settings window.  Getting this wrong would cause the ACE to only show up in Advanced
  • None: This is specifying that the permissions do not need to be applied to sub-folders.
  • Allow: This is simple:  Allow or Deny

Once you have the ACE created, add it to the ACL that was pulled from the folder with the SetAccessRule call.  I’ve tested the None option against the other possibilities for propagation.  This behaves as I expect it to.  If another user (either a Domain Admin or the Scan user) creates a file, the primary user will have modify rights to it, but not full control.  This is what I want.

Note that this only adds it in memory, so the last step is to apply that ACL to the folder:


Set-Acl $FolderPath $ACL

Putting it together:

Import-Module ActiveDirectory

# The root folder 
$ScanRoot = "D:\Shares\Scans"

# The OU these users are located in:
$OU = "OU=Scan Users,OU=Corp,DC=Contoso,DC=com"

# Get all of the users in the OU
$users = Get-ADUser -Filter * -SearchBase $OU | Select-Object Name, samAccountName

# Iterate through each user and create a folder, if necessary
ForEach ($user in $users)
{
    # The full path to the folder
    $FolderPath = "$ScanRoot\$($user.SamAccountName)"

    # Check if the folder exists
    $Exists = Test-Path $FolderPath

    if( $Exists -eq $False )
    {
        Write-Host "Folder $FolderPath does not exist, creating".
        New-Item $FolderPath -ItemType Directory
    }

    $ACL = (Get-Item $FolderPath).GetAccessControl('Access')
    $ACL.Access | %{$acl.RemoveAccessRule($_)} | Out-Null

    # Add the user
    $ACE = New-Object  System.Security.AccessControl.FileSystemAccessRule("$($user.samAccountName)","Modify","ContainerInherit,ObjectInherit","None","Allow")
    $ACL.SetAccessRule($ACE)

    Set-Acl $FolderPath $ACL
}

Comments/corrections welcome.