The Surly Admin

Father, husband, IT Pro, cancer survivor

User Termination Automation – Part 1

Having written about New User Automation here and here, it only made sense to start working on User Termination Automation.  Read on to see how I accomplished this.

Project Scope

At SeraCare we have a two-step termination process.  First HR notifies us by going into a Sharepoint Custom List I created and let’s us know about the leaving user, which includes information like who their supervisor is, does that supervisor want to review email and files, do they want an Out Of Office message say the user is no longer with us, etc.  This, in turn, triggers a notification email to our help desk software and generates a ticket.  We then wait for the last day and time, and begin our process.

On “T” day, we disable the user, remove them from all of their groups, set their Out Of Office message, forward any new mail to the terminating user to their supervisor, add the supervisor to have full access to their mail file and move all of their user files to a central review folder with permissions to just the supervisor.  There’s actually a ton of other things we do too, but this is all the stuff that lies within the Windows umbrella.  Clearly a process that screams for automation!

Phase 1

First thing that needed doing is I wanted to get the meat and potato’s of the script up and working, then I wanted to refine the user experience later.  That meant making some assumptions – user would always get an Out of Office message, supervisor would always want to review email and user files.  This allows me to focus on the “hard” part of the script and that’s making all of these pieces work.  So to start, I just took the input from the New User Automation script and repurposed it here.  That way I can get the terminating user name, supervisor user name and the Spiceworks ticket #.

Write-Host "********************************************************************"
Write-Host "**             SeraCare Phase 1 Termination Script                **"
Write-Host "********************************************************************"
If ($User -eq "")
{ $User = Read-Host "Please enter User Name of outgoing employee"
If ($Supervisor -eq "")
{ $Supervisor = Read-Host "Please enter $($User)'s supervisor's User Name"
If ($Ticket -eq "")
{ $Ticket = (Read-Host "Spiceworks Ticket #")
Write-Host "`nUser:                $User"
Write-Host "Supervisor:          $Supervisor"
Write-Host "Spiceworks Ticket:   $Ticket`n"
$Answer = Read-Host "Is this the information you want to use (y/N)"
If ($Answer.ToUpper() -ne "Y")
{ Write-Host "`n`nOK.  Please rerun the script and reenter the data correctly.`n"

From here we have all of the information we need (plus a little more I loaded from parameters).  Next up I need to query AD for some more information, primary being the first name, last name combination in a pretty format.  I could just use the DisplayName property from the user object, but the problem is that different AD’s display this information in different formats depending on the settings the administrator specifies.  To get around that I just use the GivenName and SurName properties and put them together in a little function.

#region Functions
Function Get-DisplayName ($AccountName)
{    $UserName = Get-ADUser $AccountName
     $Result = "$($UserName.GivenName) $($UserName.SurName)"
     Return $Result

Why did I use #Region?  Just to make the code a little neater, you can read more about it here.  I used Get-Mailbox to verify if the user exists or not, I use the Break statement to exit out of the script if either the user name provided or the supervisor username provided is $null.  Next comes a series of Exchange commands to make the settings I want.  $Mailbox is an object variable I created earlier in the script (when checking if the user exists or not) representing the mailbox of the terminating user.

Write-Host "`nSetting Out of office..."
$Mailbox | Set-MailboxAutoReplyConfiguration -AutoReplyState Enabled -InternalMessage $Message -ExternalMessage $Message
Write-Host "Now forwarding email to $SupFullName..."
$Mailbox | Set-Mailbox -DeliverToMailboxAndForward:$true -ForwardingSmtpAddress $SupEmail
Write-Host "Now setting permissions so $SupFullName has access to the mailbox..."
$Mailbox | Add-MailboxPermission -user $Supervisor -AccessRights FullAccess

There, we’ve done all the work on the mailbox now we have to work on the user object.

$ADUserObject = Get-ADUser $User -Properties MemberOf,HomeDirectory
ForEach ($Group in $ADUserObject.MemberOf)
     Get-ADGroup $Group | Remove-ADGroupMember -confirm:$false -members $User
Write-Host "Disabling $FullName..."
Disable-ADAccount $User

Now we’ve removed them from all of their groups and disabled the user object.  Next comes moving the user folders, which is a bit trickier.  As I’ve mentioned before at SeraCare we have 3 sites (soon to be 2, but that’s another story) each with their own file server.  How do you determine which site a user is in, and where to copy the files, since each site has its own File Review folder?  As with anything Powershell you can do things many, many ways but I decided to simply do a text search on the home directory to determine my location.

$Dir = $ADUserObject.HomeDirectory
Switch -wildcard ($Dir)
{  "*Userfolders\mf*"
      {   Write-Host "Moving user folder to \\surlyadmin.corp\shared\Data\File Review..."
          ...move files here...
          $ReviewDir = "\\surlyadmin.corp\shared\Data\File Review"
   {  Write-Host "Moving user folder to \\surlyadmin.corp\shared\gb-common\File Review..."
      ..move files here...
      $ReviewDir = "\\surlyadmin.corp\shared\gb-common\File Review"

I left a little code out here because I want to talk about it in greater detail later.  Switch is a great command for doing many different interrogations on the same variable.  In my case it’s $Dir which has the user’s home directory in it.  The key here is the -wildcard switch which allows me to do a wildcard style search on my text string.  The “*” means anything in front or anything behind.

Moving Files

There are a couple of challenges here that we need to address.  Normally you would just use the Move-Item cmdlet and move the files from the home directory location into the File Review folder, right?  Except if you try to run that from the workstation it won’t work and you’ll get an error that you can’t move items between drives.  The funny thing is the files are actually on the same drive, but that’s on the server.  When running from the workstation that’s not the case.  The obvious solution would be to use Copy-Item and then Remove-Item to clean up the old files.

But that solution brings up the question of performance.  Do you really want to copy all the files that user has down to your workstation and then back up to the server?  And then delete them all after that?  The beauty of a Move operation is that it really just goes into the directory structure and changes the path, which is why move operations on the same volume are very fast.  I want to take advantage of that speed, and don’t want to risk someone having gigs worth of data in the home directory and having to copy that from my workstation.

In reality, what I really want to do is a Move-Item command on the server, right?  It’s on the same volume and I can take advantage of the things that Move can do.  This is where remoting comes into play and using the Invoke-Command cmdlet.  First, I had to enable remoting on my files servers, which I wrote a how-to on doing it over at Spiceworks (if you like it, spice it up!) and then we can issue Invoke-Command to do the work.

Invoke-Command -ComputerName fileserver -ArgumentList $ADUserObject.SamAccountName -ScriptBlock { Move-Item -Path "d:\dfs-folders\users\$args" -Destination "d:\dfs-folders\hqdata\file review" }

One big problem with Powershell 2.0 (that 3.0 has since fixed) is that you can’t just use your variables in the -Scriptblock parameter.  You instead have to pass them using the -ArgumentList parameter and then either use $args or Param inside the scriptblock.  But once you have that, the rest of the command is simple and will execute the file move on the remote server.  Remoting is another of the truly awesome things Powershell can do that isn’t easily accomplished with other scripting languages–though you could certainly have your vbScript run PSEXEC to do something similar.

You may have noticed above that after moving the files I set $ReviewDir to the DFS path of the File Review folder.  The reason behind this was so I could then set my specific folder permissions.  I could have done this in the Invoke-Command scriptblock, and it’d be faster than running it locally but I decided not to so I could better troubleshoot problems (very difficult when remoting).

$ACL = Get-Acl "$ReviewDir\$User"
$ACL.SetAccessRuleProtection($true, $false)
$ACL.Access | ForEach { [Void]$ACL.RemoveAccessRule($_) }
$ACL.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule("$NTDomain\Domain Admins","FullControl", "ContainerInherit, ObjectInherit", "None", "Allow")))
$ACL.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule("$NTDomain\$Supervisor","Modify", "ContainerInherit, ObjectInherit", "None", "Allow")))
Set-Acl "$ReviewDir\$User" $ACL

Pretty much the same code as in the new user creation scripts.  Get-ACL on my directory, turn off inheritance, remove current permissions and put in my new permissions and then save it all with Set-ACL.


We’ve pretty much handled the nuts and bolts of the termination except for a long-winded email to the supervisor telling them what’s happened and another email sent to Spiceworks to update the ticket with what we’ve done.  But there’s still some work to do.  Phase 1 of the script got the work done, but we need to tighten things up some more.  In our termination process we can do custom Out Of Office messages, if requested and the supervisor has the ability to decide if they want to review files or email.  The challenge here is UX, and I want to build a simple GUI interface, using Powershell, for our user input.  Haven’t worked much with forms so that will be a fun challenge.

Last we have a 2 step process for termination.  After doing the 1st step we then wait 2 weeks, then finish off by deleting the user account and the user folder in the File Review folder.  Should be pretty easy, but there are definitely times that we save people past the 2 weeks so have to consider how I will handle that.

So stay tuned for Part 2 of User Termination Automation!


December 27, 2012 - Posted by | PowerShell | , ,

No comments yet.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: