One of my many tasks as a system administrator is to close user accounts after they have left their jobs. Not working in HR, I don't need to concern myself with why they are no longer employed. I simply need to process their account closures in accordance with our own policies.
Avatar

Whether the user had a company mobile device that needs to be wiped, passwords reset, documents moved, or email redirected, there will be actions you'll need to take in your organization, and more importantly, a time at which to carry out the actions.

For a small team of administrators, this can pose a potential problem. If you work for an external organization providing IT services, you may not have the ability to get in and follow all the steps in your account closure procedure because of other commitments. I am not here to judge; I am just here to explain how I do things.

My procedure uses two parts. One is built into a larger tool as a function, which I call ScheduleLeaver. The other is a standalone script called ProcessLeaver.

For the purpose of this article, I am splitting ScheduleLeaver into its own script.

First off, we need to gather some information about the user who is leaving and store that information for use at our scheduled time.

$scheduleCSV = "c:\scripts\scheduleLeaver.csv"
if(!(Test-Path $scheduleCSV))
{
    New-Item $scheduleCSV -ItemType File
    Add-Content $scheduleCSV "UserName,Date,Time,Email"
}

This section of code allows us to specify a filename to save our scheduled leavers in and if the file does not exist, create it along with the correct column headers.

Next, we use a series of "do" actions to collect some basic information about the user account.

Import-Module ActiveDirectory

Add-PSSnapin Microsoft.Exchange* 
do{
    $userName = Read-Host -Prompt "Enter UserName of User Account to Close"
    $checkUser = (Get-AdUser $userName).SamAccountName
    if(($checkUser) -ne $userName)
    {
        Write-Output "User Not Found"
    }
    else
    {
        $userAcct = 1
    }
} 
until ($userAcct -eq "1")

Here we have imported the ActiveDirectory and Exchange PowerShell tools and prompted for the username of the account we wish to close, and then checked for that username in Active Directory. If it is found, we move on; if not, we prompt to enter the username again.

Our next section is similar, except it concerns the account closure date.

do{
    $date = Read-Host -Prompt "Enter Date to Close Account"
    Write-Output "Close Account: $date"
    $confirm = Read-Host -Prompt "Confirm? Y/N"
    if(($confirm) -eq "y")
    {
        $dateAcct = 1
    }
}
until ($dateAcct -eq "1")

In this section, we prompt for a date to close the account and loop around if the date is not accepted.

The next code block is identical, except we prompt for the time to close the account.

do{
    $time = Read-Host -Prompt "Enter Time to Close Account"
    Write-Output "Close Account: $time"
    $confirm = Read-Host -Prompt "Confirm? Y/N"
    if(($confirm) -eq "y")
    {
        $timeAcct = 1
    }
}
until ($timeAcct -eq "1")

The next block is slightly more complex, as we ask if we need to forward the user's email, and confirm we have entered a valid recipient address.

do{
    $emailF = Read-Host -Prompt "Forward Email?"
    if(($emailF) -eq "y")
    {
        $recipient = Read-Host "Enter Recipient Address"
        $recipientCheck = Get-Mailbox $recipient
        if(($recipientCheck) -eq $null)
        {
            Write-Output "Address Not Found"
        }
        else
        {
            $emailAcct = 1
        }    
    }
    else
    {
        $emailAcct = 1
    }
}
until ($emailAcct -eq "1")

Next, we add on a small summary of what we have entered.

Write-Output "Summary"
Write-Output "Close Account : $userName"
Write-Output "Close Date    : $date"
Write-Output "Close Time    : $time"
Write-Output "Forward Email : $recipient"

We then confirm this information and enter it into our CSV File.

$confirmSum = Read-Host -Prompt "Confirm? Y/N"
if(($confirmSum) -eq "y")
{
    Add-Content $scheduleCSV "$userName,$date,$time,$recipient"
}

You should end up with a file that looks like this.

ScheduleLeaver CSV

ScheduleLeaver CSV

In the next section, we build the second script, ProcessLeaver. This will actually take the required actions.

Our first task is to import our CSV File. We're then going to empty the contents of the file. You will see why in a moment.

Import-Module ActiveDirectory
Add-PsSnapin Microsoft.Exchange*
$scheduleLeaver = "c:\scripts\scheduleLeaver.csv"
$csv = Import-CSV $scheduleLeaver
Clear-Content $scheduleLeaver
Add-Content $scheduleLeaver "Username,Date,Time,Email"

At this point, the script stores everything in our CSV file in $scheduleLeaver and empties the file itself.

Why do we do that?

The next code block checks whether the current date is before or after the user’s scheduled leave date. If it is after, we can process the account closure. If it is still before the scheduled leave date, we can simply add the user back to the schedule file.

foreach ($leaver in $csv)
{
    $date = $leaver.Date
    $time = $leaver.Time
    $leaverDate = "$date $time"
    $checkDate = (Get-Date -format "dd/MM/yyyy HH:mm").ToString()
    if(($leaverDate) -le $checkDate)
    {
    
    }
    else
    {
        $userName = $leaver.UserName
        $email = $leaver.Email
        Add-Content $scheduleLeaver "$userName,$date,$time,$email"
    }
}

The above section builds a $leaverDate string and compares it to a Get-Date string to determine whether we should process the leaver. If we should not, the script adds the user's information back to the CSV File.

Next, we can move onto the fun part.

What do you want to accomplish with your account closure?

In my example I am keeping things simple, with the following:

  • Reset password
  • Disable account
  • Disable ActiveSync
  • Hide from address list
  • Forward email

You can of course get more creative if you wish. For example, you could add in an email notification to HR to say the account has been closed and include new login information, instructions on how to access the account's old email or documents, and so on. The possibilities are endless.

if(($leaverDate) -le $checkDate)
    {
        $userObj = Get-AdUser $leaver.UserName -properties EmailAddress
        $newPassword = "SomeRandomPassword357203875!!"
        $pwd = ConvertTo-SecureString $newPassword -AsPlainText -Force
        Set-AdAccountPassword $userobj.SamAccountName -NewPassword $pwd -Reset
        Disable-ADAccount $leaver.SamAccountName -confirm:$false
        Set-CasMailbox $userObj.EmailAddress -ActiveSyncEnabled $false
        Set-Mailbox $userObj.EmailAddress -HiddenFromAddressListEnabled $true
        if(($leaver.Email) -ne "")
        {
            Set-Mailbox $userObj.EmailAddress -forwardingSMTPAddress $leaver.Email -deliverToMailboxandForward $false
        }
}

All we need to do now is save both of these files with a ps1 extension and set up a scheduled task to execute the ProcessLeaver.ps1 file at a given time of the day. For me, this is at 6pm from Monday to Friday.

If you are using Office 365, you can still use this method. You just need to add in the code to connect up to Office 365 PowerShell. After that, your Get-Mailbox commands will run against your Exchange Online instance instead of a local on-premises Exchange organization.

Take a look at the comments below for some suggestions on adding logging and error recovery!

The full script is below.

Subscribe to 4sysops newsletter!

$scheduleCSV = "c:\scripts\scheduleLeaver.csv"
if(!(Test-Path $scheduleCSV))
{
    New-Item $scheduleCSV -ItemType File
    Add-Content $scheduleCSV "UserName,Date,Time,Email"
}
Import-Module ActiveDirectory
Add-PSSnapin Microsoft.Exchange*
do{
    $userName = Read-Host -Prompt "Enter UserName of User Account to Close"
    $checkUser = (Get-AdUser $userName).SamAccountName
    if(($checkUser) -ne $userName)
    {
        Write-Output "User Not Found"
    }
    else
    {
        $userAcct = 1
    }
}
until ($userAcct -eq "1")
do{
    $date = Read-Host -Prompt "Enter Date to Close Account"
    Write-Output "Close Account: $date"
    $confirm = Read-Host -Prompt "Confirm? Y/N"
    if(($confirm) -eq "y")
    {
        $dateAcct = 1
    }
}
until ($dateAcct -eq "1")
do{
    $time = Read-Host -Prompt "Enter Time to Close Account"
    Write-Output "Close Account: $to,e"
    $confirm = Read-Host -Prompt "Confirm? Y/N"
    if(($confirm) -eq "y")
    {
        $timeAcct = 1
    }
}
until ($timeAcct -eq "1")
do{
    $emailF = Read-Host -Prompt "Forward Email?"
    if(($emailF) -eq "y")
    {
        $recipient = Read-Host "Enter Recipient Address"
        $recipientCheck = Get-Mailbox $recipient
        if(($recipientCheck) -eq $null)
        {
            Write-Output "Address Not Found"
        }
        else
        {
            $emailAcct = 1
        }    
    }
    else
    {
        $emailAcct = 1
    }
}
until ($emailAcct -eq "1")
Write-Output "Summary"
Write-Output "Close Account : $userName"
Write-Output "Close Date    : $date"
Write-Output "Close Time    : $time"
Write-Output "Forward Email : $recipient"
$confirmSum = Read-Host -Prompt "Confirm? Y/N"
if(($confirmSum) -eq "y")
{
    Add-Content $scheduleCSV "$userName,$date,$time,$recipient"
} 
ProcessLeaver.ps1
$scheduleLeaver = "c:\scripts\scheduleLeaver.csv"
$csv = Import-CSV $scheduleLeaver
Clear-Content $scheduleLeaver
Add-Content $scheduleLeaver "Username,Date,Time,Email"
foreach ($leaver in $csv)
{
    $date = $leaver.Date
    $time = $leaver.Time
    $leaverDate = "$date $time"
    $checkDate = (Get-Date -format "dd/MM/yyyy HH:mm").ToString()
    if(($leaverDate) -le $checkDate)
    {
        $userObj = Get-AdUser $leaver.UserName -properties EmailAddress
        $newPassword = "SomeRandomPassword357203875!!"
        $pwd = ConvertTo-SecureString $newPassword -AsPlainText -Force
        Set-AdAccountPassword $userobj.SamAccountName -NewPassword $pwd -Reset
        Disable-ADAccount $leaver.SamAccountName -confirm:$false
        Set-CasMailbox $userObj.EmailAddress -ActiveSyncEnabled $false
        Set-Mailbox $userObj.EmailAddress -HiddenFromAddressListEnabled $true
        if(($leaver.Email) -ne "")
        {
            Set-Mailbox $userObj.EmailAddress -forwardingSMTPAddress $leaver.Email -deliverToMailboxandForward $false
        }
    }
    else
    {
        $userName = $leaver.UserName
        $email = $leaver.Email
        Add-Content $scheduleLeaver "$userName,$date,$time,$email"
    }
}
avataravatar
5 Comments
  1. Avatar
    Andrew Hilborne 7 years ago

    This script is a good idea, but needs work! It is the opposite of robust in the face of failures. For example: you empty the csv file, do the work, then recreate it. If the jobs fails midway, you lose the file for good. You should create a backup of the file first and atomically rename the final version when the job is successful, possibly leaving entries behind when closure hasn’t completely succeeded. You should also consider making each step in account closure idempotent: so partial failures can be re-tried at a later time.

    You also really need some logging in there. What happens when part of an account closure doesn’t happen, or, worse, some accounts are closed when they shouldn’t have been? You need evidence.

  2. Avatar Author

    I think you’re absolutely right, I don’t consider this to be a finished article. More of a good starting point. Does it accomplish the task? Yes. Is it perfect? by no means.

    My aim with PowerShell articles is to show people who may dismiss it as a means to run a command once in a while, to transition into using it every day. I am certainly no PowerShell expert but i think some people can be put off of using it by making things too complex too early. I know it did when i started to use it a few years back.

    I want people to think of new ways of doing things, and take steps to start learning how to implement their own improvements.

    https://4sysops.com/archives/logging-in-powershell-scripts/

    Rename-Item on TechNet

     

    avatar
  3. Avatar
    BI 7 years ago

    This is really great, and is something I’m working on myself. One of those projects that I’ve been meaning to perfect for awhile. *cough* six years *cough

    Hitting some problems with cleaning up group membership, mainly because I don’t understand pipelines/filters…

    Need to basically do this — empty out all group membership except one group.

    Got PS>> Get-ADPrincipalGroupMembership -Identity Some.Person | select Name | Where-Object {$_.Name -ne ‘Some Group’}

    but either that is overkill ,or I’m not understanding how to pipe those results to Remove-ADGroup (or Remove-ADPrincipleGroupMembership)

  4. Avatar Author

    I must admit i have not used that cmdlet before.

    I just had a quick play and piping the output of the ‘get’ command doesn’t seem that straight forward to me.

    So i would be inclined to do this,

    $adGroups = Get-ADPrincipalGroupMembership $userName | where-object { $_.Name -ne "Domain Users" }
    foreach ($adGroup in $adGroups)
    {
    Remove-ADPrincipalGroupMembership -identity $userName -memberOf $adGroup.Name -confirm:$false
    }
    
    

     

  5. Avatar

    Thank you Robert Pearman, this is helpful even in my small shop.

    get-mailbox -ResultSize 2000 | Measure-Object | Select-Object count

    cheers

Leave a reply

Your email address will not be published. Required fields are marked *

*

© 4sysops 2006 - 2023

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account