Auto Disabling Inactive Users with PowerShell

A number of years ago I posted some vbscript for auto disabling inactive users.  My system before was a two part system where I found the inactive users, wrote them to a text file and then my proxy system handled them.  Recently, I have been reviewing all of my scripts and moving them over to PowerShell.  (Note that I am now always expecting PowerShell 3.0 at minimum due to the improvement in native AD cmdlets.)

In addition, we have had to make sure that we are complying with PCI audit requirements so that inactive user accounts are disabled on a timely basis.  In the below PowerShell script, I have done a few different things:

  • Create an array of OU’s to look into for inactive accounts.
  • Look for users who have never logged on and were created 45 days or more ago.
  • Next, I look for users whose accounts have expired.
  • I send each user I find to my Disable-ADProxyUsers function.  This lets me do everything I want to when I disable a user account.

Here is that part of the script:

Import-Module ADProxy             
#First run just does regular accounts.            
$SearchBaseArray = "OU=User Accounts,DC=contoso,DC=org", "OU=Specialized Workstations and Users,DC=contoso,DC=org"            
            
foreach ($SearchBase in $SearchBaseArray) {               
 Write-Verbose "First, let's get any user who has never logged on and the creation date is more than 45 days"            
  $a = Get-Date            
  $b = $a.AddDays(-45)            
  $Justification = "Disabled: Auto Disabled by Proxy - User Never Logged On."            
  Get-ADUser -f {(lastlogontimestamp -notlike "*") -and (enabled -eq $true) -and (whencreated -lt $b) } `
     -searchbase $SearchBase |           
  Disable-ADProxyUser -Requestor "Proxy System" -ProcessType "AutoNeverLoggedOn" `
     -Justification $Justification -Notes "Yes"            
              
 Write-Verbose "Now let's look for inactive users.  Search AD accountinactive automatically adds 15 days to the timespan"            
  $NbDays = 104            
  Write-Debug "Get the current date"            
  $currentDate = [System.DateTime]::Now            
  # Convert the local time to UTC format because all dates are expressed in UTC (GMT) format in Active Directory            
  $currentDateUtc = $currentDate.ToUniversalTime()            
  # Calculate the time stamp in Large Integer/Interval format using the $NbDays specified on the command line            
  $lastLogonTimeStampLimit = $currentDateUtc.AddDays(- $NbDays)            
  $lastLogonIntervalLimit = $lastLogonTimeStampLimit.ToFileTime()            
  $Justification = "Disabled: Disabled by Proxy - User Account Inactive for more than 90 days."            
  Get-ADUser -f { (lastlogontimestamp -lt $lastLogonIntervalLimit) -and (enabled -eq $true) `
     -and (whencreated -lt $b)} -searchbase $SearchBase |            
  Disable-ADProxyUser -Requestor "Proxy System" -ProcessType "AutoInactive" `
     -Justification $Justification -Notes "Yes"            
              
 Write-Verbose "Finally let's look for accounts that have expired"            
  $Justification = "Account expired.  If you reenable, update the Account Expiration date."            
  Search-ADAccount -accountexpired -usersonly -searchbase $SearchBase |             
   Disable-ADProxyUser -Requestor "Proxy System" -ProcessType "AutoExpired" `
     -Justification $Justification -Notes "Yes"            
            
}

Finally, I send any users I find to a custom AD module to disable the user. I have made this into a function that is part of a larger module that I use to manage most changes I make in AD.  This custom function handles the disable by doing the following:

  • Disables the account
  • Updates the description with information on when the account was disabled and who disabled it.
  • Puts the disabled date into a custom schema field called contosoDisabledDate.  I then use the date in this field to auto delete accounts after they have been disabled for 60 days.
  • Set msnpAllowDialin to false.
  • Move to a Pending Delete OU.
  • Record information about this work in a SQL table so I can produce a monthly report on all AD changes.  I’ll share some of my code on this in a future blog post.

One of the benefits of this function (as part of my module) is that I can send users to disable using the PowerShell pipeline.  If you aren’t familiar with running functions, you can save below as a file and dot source it.  (You can find more information about dot sourcing by reading this blog article: http://mctexpert.blogspot.com/2011/04/dot-sourcing-powershell-script.html )

    function Disable-ADProxyUser {            
    <# 
     .Synopsis
      Disables Users in AD, moves them to pending delete and updates them with the correct information.
    
     .Description
      This will disable a user using our processes in place here in USW.  This includes disabling the account,
      moving it to Pending Delete OU and updating appropriate fields (description - contains date user was disabled
      and who disabled it, info - has the reason the user was disabled, division - old location for placing disabled
      date, and TSADisableDate - new location for storing the disable date using a Large Integer).  Finally it 
      reports all of these updates to the ADProxy reporting database.  
      
      This command accepts the user name via either direct parameter input or via pipeline.
      
     .Parameter Requestor
      The name of the person requesting the disable.
    
     .Parameter ProcessType
      This lets us know if it was done manually or through some other process.
      
     .Parameter Justification
      This is the reason that the account is being disabled.
      
     .Parameter Notes
      If we also want to disable the users Notes account, this field needs to be marked with a "Y".
       
     .Parameter Admin
      Setting this to "Y" will disable the user account and move it to the Pending Delete IT Admin OU.
       
     .Parameter WorkOrder
      The Service Desk Work Order number related to this disable.
    
     .Example
       # Disable a user and record the disable information to the ADProxy report DB.
       $LDAPFilter = "(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(lastLogonTimeStamp<=" + $lastLogonIntervalLimit + "))"
       Get-ADUser -SearchBase "OU=User Accounts,DC=usw,DC=ad,DC=salvationarmy,DC=org" -LDAPFilter $LDAPFilter -Properties * | Disable-ADProxyUser -Requestor "Proxy System" -ProcessType "Auto" -Justification "Disabled: Auto Disabled by Proxy - Inactive 75 days."
    
     .Example
       # Disable an individual user
       Disable-ADProxyUser -UserName "Joe.Bob" -Requestor "Proxy System" -ProcessType "Auto" -Justification "Disabled: Auto Disabled by Proxy - Inactive 75 days." -Notes "Y"
       
      .Example
      	#Disable a user with pipeline from a text file containing one user name per line
      	Get-Content names.txt | Disable-ADProxyUser -Requestor "Doug Neely" -ProcessType "ADCleanup" -Justification "User is inactive."
    #>            
    param(            
     [Parameter(Mandatory=$true,ValueFromPipeline=$True)]            
     [string] $UserName,            
        [parameter(Mandatory=$true)]            
        [string] $Requestor,            
        [parameter(Mandatory=$true)]            
        [string] $ProcessType,            
        [parameter(Mandatory=$true)]            
        [string] $Justification,            
        [ValidateSet("Y","N")]            
        [string] $Notes,            
        [ValidateSet("Y","N")]            
        [string] $Admin,            
        [string] $WorkOrder            
        )            
        BEGIN {            
        "Disabling User Accounts:"             
     #Set the termination date format used in the AD Description            
     $TerminationDate = Get-Date -Format "MMMM dd yyyy"            
     $TerminationDate = [string]$TerminationDate            
     $TermDateDivision = Get-Date -Format d            
     #The ContosoDisableDate field in AD is Large Integer.  Converting todays date to the Large Interval format.            
     $ContosoDisableDate = (Get-Date).ToFileTime()            
     }            
     PROCESS {            
                   
      foreach ($User in $UserName) {            
       $User            
          $ADAccount = Get-ADUser -identity $User -properties *            
          $ADSAM = $ADAccount.SamAccountName            
          $ADCompany = $ADAccount.Company            
          $ADDepartment = $ADAccount.Department            
        $ADEmployeeNumber = $ADAccount.EmployeeNumber            
        $ADEmployeeType = $ADAccount.EmployeeType            
       $ADLastLogon = $ADAccount.LastLogonTimestamp            
       If ($ADLastLogon -ne "") {            
        #$ConvertedLastLogin = [DateTime]::FromFileTime( [Int64]::Parse($ADLastLogon) )            
       }            
       $ADDescription = "Disabled on " + $TerminationDate + " by " + $Requestor            
       $DBReason = $Justification            
       #$Justification = "Inactive computer - Last logon: " + $ConvertedLastLogin            
       If ($ProcessType -eq "HR"){            
        $Justification = "User terminated by HR. Do not reenable unless HR rehires this user. $DBReason"            
       }            
       If ($ProcessType -eq "AutoInactive") {            
        $WorkOrder = " "            
        $Notes = "Yes"            
     
       }            
                      
       #Disable Account            
       Get-ADUser -identity $ADSAM -properties *| Disable-ADAccount            
       #Set AD Fields            
       Get-ADUser -identity $ADSAM -properties *|             
         Set-ADUser -Description $ADDescription            
        Get-ADUser -identity $ADSAM -properties *|             
         Set-ADUser -clear info,division,ContosoDisabledDate            
        Get-ADUser -identity $ADSAM -properties *|             
         Set-ADObject -ADD @{info=$Justification;division="$TermDateDivision";ContosoDisabledDate=$ContosoDisableDate}             
        Get-ADUser -identity $ADSAM -properties *|            
         Set-ADuser -replace @{msnpallowdialin=$false}            
        #Move to Pending Delete            
      
         Get-ADUser -identity $ADSAM -properties *|             
          move-ADObject -TargetPath 'ou=Pending Delete,dc=contoso,dc=org'            
               
             
        #dbo.DisableUsersReport            
        #ProcessType, Username, Company, Department, Reason, LotusNotes, WorkOrder, Requestor, EmployeeNumber, EmployeeType, DateTime            
        $DBColumns = "ProcessType, Username, Company, Department, Reason, LotusNotes, WorkOrder, Requestor, EmployeeNumber, EmployeeType, DateTime"            
        $DBValues = "'$ProcessType','$ADSAM','$ADCompany','$ADDepartment','$DBReason','$Notes','$WorkOrder','$Requestor','$ADEmployeeNumber','$ADEmployeeType'"            
        $DBValues            
        Edit-ADProxyDBReport -Table "dbo.DisableUsersReport" -DBColumns $DBColumns -DBValues $DBValues            
                  
        #Clear values at the end            
        $ADAccount = $null            
        $ADSAM = $null            
        $ADCompany = $null            
        $ADDepartment = $null            
        $ADEmployeeNumber = $null            
        $ADEmployeeType = $null            
        $ADLastLogon = $null            
      }            
     }            
     END { "Complete Disable ADProxy User"}            
    }            
    Export-ModuleMember -function Disable-ADProxyUser