Don Jones

Contents

The Sample Environment
Creating the Folder
Assigning Permissions

In this installment of Windows PowerShell, I am picking up where I left off last month, and continuing to create a script that helps provision new users in an Active Directory environment. If you haven't read the previous installment yet, I recommend that you first read that column before continuing on here.

At this point, I have a master provisioning function that contains four sub functions. The master function looks like this:

Function Provision {
  PROCESS {
    CreateUser $_
    CreateHomeFolder $_
    AddToGroups $_
    UpdateAttributes $_
  }
}

I've already created variations of the CreateUser function. In this installment, I am focusing on the CreateHomeFolder function. I will use this function to create the user's home folder and apply the appropriate access control list (ACL) entries to it.

Windows PowerShell Q&A

Q Does Windows PowerShell distinguish between single quotes and double quotes around strings of characters?

A Yes. With all but one exception, single and double quotes behave identically. That is, both of these commands will assign the string "Microsoft" to the variable $var:

$var = 'Microsoft'
$var = "Microsoft"

However, within double quotes only, the shell looks for the dollar sign ($) character. If it finds it, the shell assumes that the characters following the dollar sign are a variable name, and it'll replace the variable with that variable's contents:

$var1 = 'Windows'
$var2 = "Microsoft $var1"

After running these two commands, $var2 would contain "Microsoft Windows" ($var1 was replaced with its contents). As a best practice, stick with single quotes unless you explicitly need this variable-replacement feature.

The Sample Environment

Like all of the child functions called by the Provision function, CreateHomeFolder is accepting a hashtable as its input. Since creating a home folder only really requires me to know the user's logon name, I can use the ['samAccountName'] hashtable key. I used this key in last month's article, as well, since the samAccountName attribute is required when creating a new Active Directory user account.

I also need to know where the home folder will be created, and I need to have network access to that location. For this example, I will assume that user home folders are all stored on a specific server, based upon the user's logon name. For example, everything starting with A through D is on server HomeFolders1, while folders for E through H are on HomeFolders2. Using this structure, my example should provide a structure that you can expand on to work within your own environment. For this discussion, I'm not going to worry about users whose names start with I through Z—the pattern will be evident, and I'd like to keep the example simple and conserve some space.

In my sample environment, each of these servers has an administrative share called $Homes, which includes all of the user home folders. And I'll assume that administrators have the ability to create new folders and that my script is being run by an administrator. Finally, I'll assume that all of the servers involved are either in the same domain, or are in trusting domains, so that they will trust the administrator account that is being used to run the script.

Creating the Folder

Creating the folder is relatively easy. First, I need to decide which server I will create the folder on, like so:

Function CreateHomeFolder {
  Param($userinfo)
  $server1 = 'a','b','c','d'
  $server2 = 'e','f','g','h'
  Switch ($userinfo['samAccountName'].    substring(0,1).tolower()) {
    { $server1 –contains $_ } 
      { $homeserver = '\\homeserver1\'; break; }
    { $server2 –contains $_ } 
      { $homeserver = '\\homeserver2\'; break; }
  }
}

So what exactly is happening here?

  • First, I create an array of letters that each home folder server supports. For this example, I've created two arrays, $server1 and $server2, and assigned them the letters I've decided they will host. This approach makes it easy for me to change letter assignments in the future, as well as to add more servers if necessary.
  • I then use the Switch construct to evaluate a series of possible conditions. I evaluate the first character of the user's samAccountName attribute, after converting the character to lowercase.
  • For each home server that I have, I add a condition to the Switch construct. The condition checks to see if the first letter of the user's samAccountName (which is contained in the special $_ variable at this point) exists within that server's array of letters. If it does, then the $homeserver variable is populated with the start of a Universal Naming Convention (UNC) path for that server. The "break" keyword prevents further conditions from being evaluated once I've found the correct server. Note that, as written, this example will not work for users whose names start with letters I to Z—you would need to add conditions for that range of letters.

Next, I simply have Windows PowerShell create the folder, so the function now looks like this:

Function CreateHomeFolder {
  Param($userinfo)
  $server1 = 'a','b','c','d'
  $server2 = 'e','f','g','h'
  Switch ($userinfo['samAccountName'].
    substring(0,1).tolower()) {
    { $server1 –contains $_ } 
      { $homeserver = '\\homeserver1\'; break; }
    { $server2 –contains $_ } 
      { $homeserver = '\\homeserver2\'; break; }
  }
  Mkdir ($homeserver + '\$Homes\' + $userinfo['samAccountName'])
}

The MkDir command—which is actually a built-in function that utilizes the New-Itemcmdlet—accepts UNC paths. So I'm combining the correct home folder server name with the $Homes administrative share and the user's samAccountName. If the user's samAccountName is DonJ, then MkDir will create \\server1\$Homes\DonJ.

Assigning Permissions

The new folder will, by default, inherit the permissions of its parent folder. So for this example, I'm assuming that the parent folder has the proper base permissions that I would want to assign to any new user home folder. For example, that permission might assign Full Control to the built-in System account, as well as to the Domain Admins group or the local Administrators group, but that permission probably would not include any other users. So all I need to do now is make sure that my new user account has permissions for the home directory I just created.

Now, please don't get mad, but I have to say that this is not a task for which Windows PowerShell is currently well-suited. I know, I'm sorry. It's just that Windows file permissions are terrifically complicated little beasts. You have an access control list (ACL), which contains access control entries (ACEs). Each ACE combines an allow/deny flag, permissions (such as Read or Write), and the GUID of a security principal (such as a user or group). This is a lot of information that has to work together.

The shell does offer Get-ACL and Set-ACL cmdlets, but modifying an ACL requires you to get the ACL, mess around with Microsoft .NET Framework objects to modify the ACL, and then drop the modified ACL back onto the file or folder. It's a little too low-level for my tastes. I could potentially use Windows Management Instrumentation (WMI), but that would work in pretty much the same way.

Fortunately, Microsoft has wrapped up a lot of that complexity into a convenient, easier-to-use tool called Cacls.exe, which I bet you're familiar with, as it's been around forever. Using it will not only save me some unnecessary effort, but will also demonstrate how Windows PowerShell works perfectly well with external command-line tools (Cacls, Ping, Ipconfig, and so forth). If you already know how to use one of these tools, please keep using it in Windows PowerShell. One of the most important concepts with Windows PowerShell is that you don't have to relearn everything from scratch.

(By the way, I know Cacls.exe is deprecated. It just has a slightly simpler syntax than the replacement ICacls.exe, so I'm using it. You can tweak this to use ICacls.exe if you like.)

Going by the help file for Cacls.exe, I find that I need a command that looks something like this:

cacls \\server1\homes\DonJ /e /g DOMAIN\DonJ:R
cacls \\server1\homes\DonJ /e /g DOMAIN\DonJ:W

This will assign Read and Write permissions to our new user, without overwriting the other permissions already on the folder.

One problem, however, is that I want to use variables in this command, rather than static values. Putting shell variables into external commands can be tricky sometimes. There are a number of approaches, but the one I like is to create the entire command string in a shell variable and then use Cmd.exe to execute that string. It looks like this:

$cacls1 = "cacls "+$homeserver+"$Homes\"
  +$userinfo['samAccountName']+" /E /G "
  +$user+":W"
$cacls2 = "cacls "+$homeserver+"$Homes\"
  +$userinfo['samAccountName']+" /E /G "
  +$user+":W"
cmd /c $cacls1
cmd /c $cacls2

Granted, this may not be the prettiest thing in the world, but it gets the job done. It also accomplishes my ulterior motive of demonstrating how complex external command-line tools can be parameterized using shell variables. I can drop these four lines of code right into my function after the MkDir command and the function is complete.

All I have left to do at this point is to create the last two functions in my master Provision function: AddToGroups and UpdateAttributes. I will show you how to do that in the next installment of the Windows PowerShell column.

Don Jones is a co-founder of Concentrated Technology (ConcentratedTech.com), where he blogs weekly about Windows PowerShell, SQL Server, App-V, and other topics. Contact him through his Web site.