Windows PowerShellAutomating User Provisioning, Part 4

Don Jones

Contents

Deciding On Input
Populating Groups
Additional Attributes
Taking It Further
Some Final Thoughts

This is the conclusion of a four-part series in which I've shown you how to automate the process of provisioning new users in Active Directory. So far, I've demonstrated how to create new user Active Directory accounts, create their home folders, and assign permissions to these home folders. Now, I need to add the users to some domain groups and update a few directory attributes.

To quickly refresh your memory, I am using a "master function" to coordinate all these efforts, which I explained in Part 1 of this series. The master function looks like this:

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

In Part 2 of the series, I created the CreateUser function, and in Part 3, I covered the CreateHomeFolder function. This month, I will deal with the AddToGroups and UpdateAttributes functions.

Deciding On Input

All four of my functions get a hashtable that contains user attributes. My original example, in Part 1, read those attributes from a Comma-Separated Values (CSV) file. At the time, I didn't go into much depth on how I planned to convey what domain groups a user should be added to. There are a lot of ways I could potentially do this. For example, I could have my CSV file include a single column named Groups and populate that column with some delimited list of names, like this:

"Domain Users,Sales,Marketing,East"

Or, I could have a separate column in the CSV for each group and then on the data rows put a 1 when the user should belong to that group and a 0 for groups I don't want the user to belong to. It might look like this in the CSV:

Domain Users,Sales,Marketing,East
1,1,0,0
1,0,1,1

I suspect that the second approach might be easier for a less-technical person (perhaps someone in the Human Resources department) to work with since it looks a bit more like a checklist. This is also something you could easily create in a Microsoft Access table or a Microsoft Excel spreadsheet and then export to a CSV file, retaining this format. So for this example, I'm going to go with the second layout.

As for the "additional attributes," I'll continue to assume those are coming from specific columns in the CSV file. For example, if I want to populate the postalCode attribute, then let's assume that I've included an appropriate column in the CSV file, and that my Import function (which I discussed in Part 1 of this series) is reading that column into the hashtable along with the other columns.

That means my hashtable may contain something like $userinfo['Domain Users'], which would be 1 if I want the user to belong to that group. The hashtable might also contain $userinfo['postalCode'] with the appropriate value for each new user.

Populating Groups

My AddToGroups function needs to accommodate every possible group that might be listed in my input file. I'm basically just going to check for each group to see if this user has a 1 or 0, and if the user has a 1, then I'll add the user to the group. Since the number 0 is usually synonymous with the Boolean value False, while anything non-zero (such as 1) is synonymous with True, that will make the comparisons pretty easy. So the function looks like this:

Function AddToGroup {
  Param($userinfo)
  If ($userinfo['Sales']) { Add-QADGroupMember 
    -identity 'DOMAIN\Sales' '
    -member ('DOMAIN\'+$userinfo
     ['samAccountName']) }
  If ($userinfo['East]) { Add-QADGroupMember 
    -identity 'DOMAIN\East' '
    -member ('DOMAIN\'+$userinfo
     ['samAccountName']) }
  If ($userinfo['Marketing]) { Add-
    QADGroupMember 
    -identity 'DOMAIN\Marketing' 
    -member ('DOMAIN\'+$userinfo
     ['samAccountName']) }
}

And so forth. Keep in mind that we're still using the Quest AD Cmdlets, which are free from www.quest.com/powershell.

All that repetitive code bugs me. I think what I'd like to do instead is create an array of possible group names and then enumerate through that array. That way, adding and removing groups in the future doesn't require me to copy and paste code—I'd just have to modify the array. So now the function looks like this:

Function AddToGroup {
  Param($userinfo)
    $groups = 'Sales','Marketing','East'
  Foreach ($group in $groups) {
    If ($userinfo[$group]) { 
      Add-QADGroupMember
        -identity 'DOMAIN\'+$group
        -member ('DOMAIN\'+$userinfo
         ['samAccountName']) }
}
}

That's a bit leaner and cleaner, and it will easily accommodate future changes.

Additional Attributes

Finally, I need to deal with any additional attributes that I want to set. Here I am going to use the same approach: Create an array of allowable attribute names and then enumerate through that array. In this case, I'm going to allow for a situation where one or more attributes are blank for a given user, like so:

Function UpdateAttributes {
  Param($userinfo)
    $attribs = 'postalCode','title',
      'physicalDeliveryOfficeName'
  Foreach ($attrib in $attribs) {
    If ($userinfo[$attrib] –ne '') {
      $update = @{$attrib=$userinfo
       ['attrib']}
    Set-QADUser
      -identity 'DOMAIN\'+$userinfo
       ['samAccountName'] 
      -objectAttributes $update
    }
  }
}

Because the Set-QADUser cmdlet actually accepts a hashtable for the –objectAttributes parameter, you might think I can just feed it the $userinfo hashtable. However, I have things in there, such as group names, that aren't Active Directory attribute names so that trick wouldn't work. Furthermore, I don't want to send any empty attributes, so I want the ability—as I've done with this function—to run through all the attributes first and then update only the attributes that actually have values in the original CSV file.

Taking It Further

There are obviously a lot of things you could do to expand this script. Here are a few ideas that come to mind:

  • Instead of retrieving information from a CSV file, you could get it from a database (such as a personnel database). You just need to replace the Import function, as discussed in Part 1 of this series.
  • You could perform other provisioning tasks. You just need to add more functions to the master Provision function. Remember to keep each major task in its own subordinate function and you'll make things easier to write, debug, and maintain.
  • You could write out a log file of provisioning activity. For example, you can use Out-File to append text to a log file within each of the subordinate functions.
  • Using the techniques covered in this series, you can write a reprovisioning script to reassign groups, permissions, and Active Directory attributes when a user moves to a different position within the company.

Windows PowerShell Q&A

QWhat Microsoft products can be managed with Windows PowerShell cmdlets?

ACurrently, the list includes Exchange Server 2007, System Center Virtual Machine Manager, System Center Operations Manager, and System Center Data Protection Manager. SQL Server 2008 also includes some Windows PowerShell cmdlets, although they're largely geared toward enabling the execution of Transact-SQL statements (Transact-SQL being the native scripting language in SQL Server).

But there's no reason to stop there. You can also manage Active Directory using a free set of cmdlets from www.quest.com/powershell; cmdlets for Internet Information Services (IIS) 7.0 are under development; and there are even cmdlets for non-Microsoft products, such as VMWare, IBM WebSphere MQ, and more. You can also use existing command-line tools to manage Windows components, such as IIS, DNS, DHCP, Windows Firewall, and more.

Some Final Thoughts

I hope you'll find the completed script and all its subordinate functions to be, at the very least, a useful starting point in creating your own provisioning scripts. By way of conclusion, I want to offer a few comments on some of the decisions I've made throughout.

I chose to use hashtables for the user information rather than custom objects because hashtables provide a somewhat easier way to enumerate through a set of properties when I don't know in advance what all those properties will be. In other words, hashtables are a bit easier to use when I know that I will need to add more attributes, and when I might not use the same attributes every time I run the script.

I chose to use Cacls.exe to modify folder permissions because, as I described in Part 3 of this series, it's just easier than the shell's *-ACL cmdlets. Also, using Cacls.exe allowed me to demonstrate one way that you can parameterize external command-line tools by using shell variables.

I chose to use Quest's Active Directory cmdlets rather than ADSI because the cmdlets offer what I feel is a more native approach to manipulating the directory in Windows PowerShell. ADSI can be a pain to work with, and it forces you to use a more procedural programming style over the command-centric style I was able to use in this example.

Whether or not you choose to make the same decisions for your scripts, I hope you'll find these techniques and different approaches useful in solving some of your administrative automation challenges. I also invite you to share your suggestions and improvements with me. On my Web site, ConcentratedTech.com, you'll find a Contact page where you're welcome to send me an e-mail. And please be sure to let me know if I can share your comments in a future issue of TechNet Magazine.

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