Monthly Archives: November 2014

Automated Expiry of Group Membership

Overview

This is the third blog in a series of posts about membership management of the (security/distribution) groups in Active Directory. The first one described the PowerShell scripts used to automate creation of Active Directory objects (such as groups and users, adding group members, etc.). In the second one I’ve presented a PowerShell script which creates an interface or a GUI tool to help group managers to manage groups in Active Directory. Finally, in this blog I will add the expiry component to the GUI tool and enable the group managers to automate expiry of the groups’ members.

Links to the previous groups’ management posts:
http://www.alexcomputerbubble.com/delegate-management-of-a-group-part-one/
http://www.alexcomputerbubble.com/delegate-management-of-a-group-group-manager-application-part-two/

The idea came to me after reading Technodrone blog which talks about setting a time limit on group membership.

I wanted more options for a group manager. For example, an option to select just one or a few members from a group and set the same or different expiration date for each individual member; or an option to set the same expiry date for all the members of a group and apply it to just selected members or to all newly added members as well. In order to provide these options, I’ve added the expiry tab to the Group Manager Application and created a PowerShell script that runs as a Scheduled Task on the server. The picture below is an attempt to graphically describe this process:

ProcessGroupExpiry

Figure 1: Automated Expiry of Group Membership

  1. Group Manger Application runs on client computers and uses ADSI to manage groups’ membership (adding, removing members, and expiry);
  2. For each security group being set with automated expiry of group membership, a configuration file (with the same name as sec. group) is written to a Folder stored on a Network Share;
  3. PowerShell script run as a Scheduled Task, and processes all the configuration files found in a Folder on a Network Share;
  4. Expiry Log file is written (successes and errors) to a Folder on a Network Share and contains one line for each and every security group processed;
  5. Changes to the security groups are written back to the log files and (in a case that a member is being removed from the group membership) back to the Active Directory.

Download and System Requirements

To download this application (a PowerShell script turned into an executable by SAPIEN PowerShell Studio ISE); please go to the download page – Application section. There are multiple files available in this download:

– CG.Option.XML
– Manage-GroupMembersWithExpiry_x64.exe
– Manage-GroupMembersWithExpiry_x86.exe
– Process-GroupMembersExpiry.ps1

The requirements for the Group Manger Application are as follows:

  1. Microsoft PowerShell version 3.0 is required with the execution policy set to ‘remotesigned’ on all client computers where this application will be used;
  2. Install Quest One ActiveRoles Management Shell for Active Directory executable on all client computers where this application will be used;
  3. Install PowerShell version 3.0 on Server 2008 R2.

3.1. Before installing Windows Management Framework 3.0, uninstall any previous versions of Windows Management Framework 3.0.
3.2. Make sure you have installed Service Pack 1
3.3. Install the full installation of Microsoft .NET Framework 4.0 (dotNetFx40_Full_setup.exe) from the Microsoft Download Center at http://go.microsoft.com/fwlink/?LinkID=212547.
3.4. Install Microsoft .NET Framework 4.5 (dotNetFx45_Full_setup.exe) from the Microsoft Download Center at http://go.microsoft.com/fwlink/?LinkID=242919.
3.5. Install Windows Management Framework 3.0 from the Microsoft Download Center at http://go.microsoft.com/fwlink/?LinkID=240290.

Install instructions

  1. Download the package; it contains both 32-bit and 64-bit executable.
  2. On a client computer:
    2.1. Unzip the download and make sure that these two files are in the same folder: CG.Option.XML and Manage-GroupMembersWithExpiry_x64.exe (or _x86.exe).
    2.2. Edit the content of the CG.Option.XML file to reflect your environment. There are three lines to be edited as shown in the picture below.

CGOptionFigure 2: CG.Option.XML file

Firstly, you have to specify the OU that contains all the security groups that will be managed by this application. Secondly, write the DN of the security group that acts as a manager. Thirdly, specify a network path to the Network Share / Folder that will contain all the configuration and log files that this Application creates while processing groups’ members. A security group designated as a manager must have up to Modify access to this Network Folder.

    3. On a server:

3.1. From unzipped folder, place the Process-GroupMembersExpiry.ps1 script in any folder where it could be accessed by the account running a Schedule Task. In my example: “G:\Common\PowerShellScripts\Process-GroupMembershipExpiry.ps1”.Edit two lines (variables) in this script to reflect your environment: the first variable is the path to the Network Share / Folder that contains all the configuration and log files as described under 2.2.

My example: $dirLogFolder = “G:\Common\Logs”

Make sure that the account running a Scheduled Task has access to these Folders.
The second variable is a path to the Log files that will be created by this Application.

 My example: $dirSource = “G:\Common\GroupMemberExpiry”.

3.2.Create a Scheduled Task.

Start by creating a Schedule Task as shown in the picture below:

CreateTask1

Figure 3: Create Task – General tab

Set a trigger, in my example: Daily, recurring every day as shown below:

SetTriger1

Figure 4: Create New Trigger

On the Action Tab specify to start a program and type Details as shown in the picture below:

CreateTask_NewAction1

Figure 5: Create Task – Action tab, type: PowerShell.exe -ExecutionPolicy Bypass -command & (“{G:\Common\PowerShellScripts\Process-GroupMembershipExpiry.ps1}”)

Once the Task has been created (in my example named: GroupMemberExpiry), you will be able to see it the Task Scheduler Library, as shown below:

ViewTask

Figure 6: Task Scheduler Library

    A Scheduled task will run every day, and you have to make sure the account that will run the task is an Administrator or a member of the Local Security Policy “Log on as a batch job”. To good practise would be to assign a dedicated AD account to run this task. Note that the permissions for this account have important role: make sure the account you use for this job has the rights to write/delete files inside the parent directory of this script (as described under 3.1.), has access to the Active Directory OU, and the rights to change the groups’ objects contained by AD OU.

How Application Works

Application provides a tool for group managers to manage and update groups’ membership as well as to automate expiry of groups’ members. This application, in its second, updated version has two tabs: Group Membership Administration and Group Membership Expiration. Please read the second blog first to find out how this application could be used to manage group’s membership. Here, in this blog, I will give you a description of the expiry component of the application and present only the portion that enables group managers to manage expiry of groups ‘members.

In my AlexTest.Local domain, I have Groups OU which contains three sub-OUs: Accounting, Sales and Warehouse. The security group, named AFL-GroupManager, has been designated as the group manager of all security groups housed in these sub-OUs.

AD_Groups Figure 7: AlexTest.Local domain – Groups organizational unit

In the picture below, you can see the properties of two security groups in Accounting OU.

AD_GRoupManagerFigure 8: example of two security groups – Managed By tab

To manage groups’ membership expiration, please start the application and click on the Group Membership Expiration tab, as shown in the picture below.

GroupManager_ExpiryTab

Figure 9: Group Membership Expiration tab

To expose the members of the groups, you would click/select the group name from the Manged Security Groups list (in my example: AFL PQR 06), and the selected group’s members would be listed in the box on the Group Membership Expiration tab. There are two radio buttons that you could use to either set expiration for the individual members of the selected group or set the expiration date for all the (present/existing) members of the group. Under the first option, there is a button named Get Expiry Date which, when clicked, will display the expiry date for the selected group that has been configured for expiration of its members. The latter option has a check-box to include all newly added members. For example, you have a group that currently has 10 members, and you use the latter option to set expiry for all existing 10 members. You set expiry date to be 60 days from now. All current members would expire and therefore be removed on the same day, except the members that were added to this group sometime after the group’s membership expiry has been set.  If you have selected the check-box, all newly added members (even those added to the group the very last day) would expire on the same day as the rest of the group members.

See in the picture below an example of setting individual members expiration in the picture below. There are five members, and the expiry days are different for each and every member. I’ve typed the numbers next to the group’s members; made sure the check boxes had been selected and pressed the Set Group Expiration button. I could, if I wanted to, to set the same number of days for each group’s member instead.

SelectMembersToExpiry

Figure 10: Setting individual members expiration – group named: AFLVWX 08

The second example shows setting the expiration date for the present group’s members. I’ve selected the security group named AFL GHI 03 and set the expiry date to be December 20, 2014. Once the expiration has been configured and the configuration file created, the numbers will appear under Days column, showing the numbers of days left (in my example 30 days). If you open this application in next ten days, and select under this tab the group named AFL GHI 03, the Days column will display number 20 as the number of days left. Please note the configuration file named as same as this group, which has three columns, but only two (“SamAccountname”,”Number”) of them have values.

SetExpiryForAllMembers

Figure 11: Setting the expiration date for all present members

The third example is a graphical explanation of setting up the expiration date for all existing members and all the newly added members that could be added to the group by Help Desk agents. The group name is AFL DEF 02, and the configuration file, just as the grid on the Expiry Tab, shows the number 7 as the number of days set for expiry of all members, current and the newly added. Note the column with name Submitted in the configuration file. It has values set for all the members. This column is named Limit in the grid on the Expiry Tab.

SetExpiryForAllMembersAndNewOnes

Figure 12: Set expiration for existing and all newly added members

Let’s see the back-end of this application. On the server side, there are two folders that are populated with groups’ configuration files and the log files. The main engine here is the PowerShell script (named: Process-GroupMembershipExpiry.ps1) that is running as a Schedule Task. The picture below shows two folders with configuration and log files.

ShareFolderFilesFigure 13: Folders containing configuration and log files

The Process-GroupMembershipExpiry.ps1 script runs as a Scheduled task every day, and while it processes the configuration files inside the GroupMemberExpiry folder, it logs its actions (one line per security group) in the Logs folder. See the picture below.

ExpiryLog_MoreFiles

Figure 14: Expiry Log and three groups’ configuration files

The following picture shows the result of the removal of groups’ members by a script.  The group named AFL STU 07 had been configured for removal of all members, while the group named AFL ABC 01 had only one member removed – Mr. Blue. RemovalOfMembers_Log

[Figure 15: Removal of group members by script

I do apologize for the length of this post, and I hope that it will help you not just understand but implement or test this solution in your environment.





Please note: Although the author has made every reasonable attempt to achieve complete accuracy of the content, he assumes no responsibility for errors or omissions. Also, you should use this information as you see fit, and at your own risk.

Stale Computer Accounts

As noted in the blog about stale user accounts, it is very important to perform regular audits of Active Directory in order to identify stale computer accounts. There are three attributes in Active Directory that could be used to identify whether a computer account is stale: pwdLastSet, lastLogon, and lastLogonTimeStamp. Many administrators define as a starting threshold for stale computer accounts a time period that is 3 times the maximum computer password age (3 x 30 days). An account is stale if all of the attributes are over a defined threshold.

What are the three attributes?

The PwdLastSet attribute is the date and time that the password for an account was last changed. The machine account password change is initiated by a computer every 30 days by default. It is important to note that a computer account passwords as such do not expire in Active Directory. They are exempted from the domain’s password policy, and its (machine account password) changes are driven by the CLIENT (computer), and not the AD.

The LastLogonTimeStamp is the replicated version of the LastLogon attribute. This attribute is not updated every time a user or computer logs on to the domain. The decision to update the value is based on the current date minus the value of the ms-DS-Logon-Time-Sync-Interval attribute minus a random percentage of 5.

See the following blog if you wish to know how it works:
Walkthrough of a LastLogonTimeStamp update:
1. (Assuming the value of the ms-DS-Logon-Time-Sync-Interval is at the default of 14)
2. User logs on to the domain
3. The lastLogontimeStamp attribute value of the user is retrieved
4. 14 – (Random percentage of 5) = X
5. Current date – value of lastLogontimeStamp = Y
6. X ≤ Y – update lastLognTimeStamp
7. X > Y – do not update lastLogontimeStamp

The LastLogonDate is a converted version of the LastLogontimestamp attribute. It’s a locally calculated value of the LastLogontimestamp attribute value replicated in AD.

With Windows PowerShell and the Microsoft Active Directory (AD) module, the task of identifying and moving (or deleting) the stale computer accounts is an easy one. In the following sections I will present a PowerShell script that I use to either disable and move inactive computer accounts or remove disabled computer accounts. The script will filter the stale accounts based on the computer’s PasswordLastSet and LastLogonDate attributes:

$DaysInactive = 90  
$time = (Get-Date).Adddays(-($DaysInactive))
Get-ADComputer -Filter {PasswordLastSet -lt $time -and LastLogonDate -lt $time -and Enabled -eq $true})

The PowerShell Script

This script first creates an Excel Report about the computer accounts to be either disabled/moved or removed, and then continues to disable/move or remove stale accounts depending on the submitted parameters.

Here is the script named: DisableOrRemove-StaleComputerAccounts.ps1.

<# 
    Name:   DisableOrRemove-StaleComputerAccounts.ps1
    Author: Alex Dujakovic
    Date:   November 2014
    Description: Disables inactive computer accounts in Active Directory
                 Removes disabled computer accounts in Active Directory
    Examples: 

    DisableOrRemove-StaleComputerAccounts.ps1 -disableForInactiveDays 90 -WhatIf
    DisableOrRemove-StaleComputerAccounts.ps1 -disableForInactiveDays 90 -move -ouName "Stale-Accounts"
    DisableOrRemove-StaleComputerAccounts.ps1 -removeForDisabledDays 180 -WhatIf
    DisableOrRemove-StaleComputerAccounts.ps1 -removeForDisabledDays 180
    
    Requires - Version 3.0 
 #> 
            
[CmdletBinding(SupportsShouldProcess=$True,DefaultParametersetName="DisableComputer")]
 
param( 
[Parameter(ParameterSetName="DisableComputer", Mandatory=$True, Position=0)] 
[int]$disableForInactiveDays = 90, 

[Parameter(ParameterSetName="DisableComputer", Position=1)] 
[switch]$move, 

[Parameter(ParameterSetName="DisableComputer", Position=2)] 
[string]$ouName, 

[Parameter(ParameterSetName="RemoveComputer", Mandatory=$True, Position=0)] 
[int]$removeForDisabledDays = 180
)

Import-Module ActiveDirectory

# Edit these lines according to your requirement
$computersOU = "OU=Computers,OU=AlexFirstLocation,DC=ALEXTEST,DC=LOCAL"
$staleAccountsOU = "OU=Stale-Accounts,OU=AlexFirstLocation,DC=ALEXTEST,DC=LOCAL"
$strFolder = "C:\Test"
# -------------------------------------------------------------------------

If(!(Test-Path $strFolder)){
    Write-Host "Path for log files does not exist!" -ForegroundColor Red
    Return
    }
# Date format
$strDate = Get-Date -format "yyyy-MM-dd"
# Error Log for Stale Accounts
$stringErrorComputerLog = "$strFolder\Error-StaleAccountsLog-"
$stringErrorComputerLog += [string]$strDate + ".log"
# Excel Report for Disabled Stale Accounts
$reportDisabledComputerAccounts = "$strFolder\Report-DisabledComputers-"
$reportDisabledComputerAccounts += [string]$strDate + ".xls"
# Excel Report for Removed Stale Accounts
$reportRemovedComputerAccounts = "$strFolder\Report-RemovedComputers-"
$reportRemovedComputerAccounts += [string]$strDate + ".xls"

# -------------------------|Functions|------------------------------------

Function Open-ExcelFile($fileNameOpen,$action){
$Script:objExcel = New-Object -ComObject Excel.Application
$objExcel.Visible = $True
    if (Test-Path $fileNameOpen){ 
        # Open excel file 
        $Script:finalWorkBook = $objExcel.WorkBooks.Open($fileNameOpen)
        $Script:finalWorkSheet = $finalWorkBook.Worksheets.Item(1) 
    }
    else { 
        # Create excel file
        $Script:finalWorkBook = $objExcel.Workbooks.Add()
        $Script:finalWorkSheet = $finalWorkBook.Worksheets.Item(1)

    switch($action){
        "DISABLE"  {
            $finalWorkSheet.Cells.Item(1,1) = "Name" 
            $finalWorkSheet.Cells.Item(1,2) = "OperatingSystem" 
            $finalWorkSheet.Cells.Item(1,3) = "Enabled" 
            $finalWorkSheet.Cells.Item(1,4) = "Action" 
            $finalWorkSheet.Cells.Item(1,5) = "PasswordLastSet" 
            $finalWorkSheet.Cells.Item(1,6) = "LastLogonDate"
            $finalWorkSheet.Cells.Item(1,7) = "MovedToOU" 
            $finalWorkSheet.Cells.Item(1,8) = "DN-Name"           
        }
        "REMOVE"{
            $finalWorkSheet.Cells.Item(1,1) = "Name" 
            $finalWorkSheet.Cells.Item(1,2) = "Enabled" 
            $finalWorkSheet.Cells.Item(1,3) = "Action"  
            $finalWorkSheet.Cells.Item(1,4) = "LastLogonDate"
            $finalWorkSheet.Cells.Item(1,5) = "DN-Name"        
        }
        } # End switch

        $Script:range = $finalWorkSheet.UsedRange 
        $range.Interior.ColorIndex = 16 
        $range.Font.ColorIndex = 11 
        $range.Font.Bold = $True  
        $range.EntireColumn.AutoFit()
    }

} # End Open-ExcelFile function

Function Close-ExcelFile($fileNameClose){
    
    Function Release-Ref ($ref) { 
    ([System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$ref) -gt 0) 
    [System.GC]::Collect() 
    [System.GC]::WaitForPendingFinalizers() 
    } 

    if (Test-Path $fileNameClose){
        # Save excel file
        $range.EntireColumn.AutoFit()
        $finalWorkBook.Save()
        $objExcel.Quit()
        $close = Release-Ref($finalWorkBook) 
        $close = Release-Ref($objExcel)
    }
    else{
        # Save As excel file
        $range.EntireColumn.AutoFit()
        $finalWorkBook.SaveAs($fileNameClose)
        $objExcel.Quit()
        $close = Release-Ref($finalWorkBook) 
        $close = Release-Ref($objExcel)
    } 
} # End Close-ExcelFile function

Function Disable-ComputerAccounts($numberOfDaysInactive, $move, $ouName){
$DaysInactive = $numberOfDaysInactive  
$time = (Get-Date).Adddays(-($DaysInactive))

$listOfComputers = (Get-ADComputer -Filter {PasswordLastSet -lt $time -and `
 LastLogonDate -lt $time -and Enabled -eq $true} -SearchBase $computersOU -Properties * | 
Sort LastLogonDate )

# Document inactive status of AD Computer Accounts
Function Document-InactiveComputerAccounts($computerCollection){
$RowNum = 2
foreach($computer In $computerCollection){
#Name OperatingSystem Enabled Action PasswordLastSet LastLogonDate	MovedToOU	DN-Name
$finalWorkSheet.Cells.Item($RowNum,1) = $computer.Name
$finalWorkSheet.Cells.Item($RowNum,2) = $computer.OperatingSystem 
$finalWorkSheet.Cells.Item($RowNum,3) = $computer.Enabled 
$finalWorkSheet.Cells.Item($RowNum,4) = "" 
$finalWorkSheet.Cells.Item($RowNum,5) = $computer.PasswordLastSet 
$finalWorkSheet.Cells.Item($RowNum,6) = $computer.LastLogonDate
$finalWorkSheet.Cells.Item($RowNum,7) = "" 
$finalWorkSheet.Cells.Item($RowNum,8) = """$($computer.DistinguishedName)""" 
$RowNum++
}

} # End Document-InactiveComputerAccounts function

# Disable and update description field in AD
Function DisableAndUpdateDescription($computerName){
    Try{
        $computer = Get-ADComputer -Identity $computerName -Properties *
        $computer.Description += " - Disabled $numberOfDaysInactive Days Inactive"
        $computer.Enabled = $False

        Set-ADComputer -Identity $computer.DistinguishedName -Description $computer.Description `
        -Enabled $computer.Enabled  -ErrorAction Stop
        Write-Host "Disabling computer: $($computerName)" -ForegroundColor Green
            $RowNum = 2
        While ($finalWorkSheet.Cells.Item($RowNum,8).Text -ne "") {
            $excelComputerName = $finalWorkSheet.Cells.Item($RowNum,8).Text
            if ($excelComputerName -match "$value"){
			    $finalWorkSheet.Cells.Item($RowNum,4) = "Disabled"
	        }    
            $RowNum++
          }    
        }
    Catch{
        Write-Host "Error while disabling computer: $($computerName)" -ForegroundColor Red
        "$($computer.Name)- Error: $($_.Exception.GetType().FullName) - Message: $($_.Exception.Message)" |
        Out-File -FilePath $stringErrorComputerLog -Encoding ascii -Append
        }
}

# Move disabled AD Accounts
Function MoveInactiveComputerAccounts($OU,$computerMove){
    try{
        Move-ADObject -Identity $computerMove -TargetPath (Get-ADOrganizationalUnit -LDAPFilter "(Name=$OU)")
        Write-Host "Moving computer: $($computerMove)" -ForegroundColor Green
        
            $RowNum = 2

        While ($finalWorkSheet.Cells.Item($RowNum,8).Text -ne "") {
            $excelComputerName = $finalWorkSheet.Cells.Item($RowNum,8).Text
            if ($excelComputerName  -match "$value"){
			        $finalWorkSheet.Cells.Item($RowNum,7) = "$OU"
	            }    
            $RowNum++
          }

        }
    catch{
        Write-Host "Error while moving computer: $($computerMove)" -ForegroundColor Red
        "$($computerMove)- Error: $($_.Exception.GetType().FullName) - Message: $($_.Exception.Message)" |
        Out-File -FilePath $stringErrorComputerLog -Encoding ascii -Append
        }
}
$action = "DISABLE"
Open-ExcelFile -fileNameOpen $reportDisabledComputerAccounts $action
Document-InactiveComputerAccounts $listOfComputers
foreach($computerDN In $listOfComputers){
    DisableAndUpdateDescription -computerName $computerDN.DistinguishedName
    If($move){
       MoveInactiveComputerAccounts -OU $ouName -computerMove $computerDN.DistinguishedName
    }
}
Close-ExcelFile -fileNameClose $reportDisabledComputerAccounts

} #End function Disable-ComputerAccounts

Function Remove-ComputerAccounts($numberOfDaysDisabled){

$DaysDisabled = $numberOfDaysDisabled  
$time = (Get-Date).Adddays(-($DaysDisabled))

$listOfDisabledComputers = (Search-ADAccount -AccountDisabled -ComputersOnly -SearchBase $staleAccountsOU | 
Select-Object Name, Enabled, LastLogonDate, DistinguishedName | 
Where {$_.LastLogonDate -lt $time} |
Sort LastLogonDate )

# Document disabled status of AD Computer Accounts
Function Document-DisabledComputerAccounts($computerCollection){
$RowNum = 2
foreach($computer In $computerCollection){
# Name Enabled Action LastLogonDate DN-Name
$finalWorkSheet.Cells.Item($RowNum,1) = $computer.Name
$finalWorkSheet.Cells.Item($RowNum,2) = $computer.Enabled 
$finalWorkSheet.Cells.Item($RowNum,3) = "" 
$finalWorkSheet.Cells.Item($RowNum,4) = $computer.LastLogonDate
$finalWorkSheet.Cells.Item($RowNum,5) = """$($computer.DistinguishedName)""" 
$RowNum++
}

}# End Document-DisabledComputerAccounts function

# Remove disabled AD Computer Accounts
Function Remove-DisabledComputerAccounts($computerRemove){
    try{
        Remove-ADComputer -Identity $computerRemove -ErrorAction Stop -Confirm:$false
        Write-Host "Removing computer: $($computerRemove)" -ForegroundColor Green
                $RowNum = 2
            While ($finalWorkSheet.Cells.Item($RowNum,5).Text -ne "") {
                $excelComputerName = $finalWorkSheet.Cells.Item($RowNum,5).Text
                if($excelComputerName -match "$value"){
			        $finalWorkSheet.Cells.Item($RowNum,3) = "Removed"
	              }    
                $RowNum++
             }
        }
    catch{
         Write-Host "Error while removing computer: $($computerRemove)" -ForegroundColor Red
        "$($computerRemove)- Error: $($_.Exception.GetType().FullName) - Message: $($_.Exception.Message)" |
         Out-File -FilePath $stringErrorComputerLog -Encoding ascii -Append
         }

}
$action = "REMOVE"
Open-ExcelFile -fileNameOpen $reportRemovedComputerAccounts $action
Document-DisabledComputerAccounts $listOfDisabledComputers
foreach($computerDN In $listOfDisabledComputers){
    Remove-DisabledComputerAccounts -computerRemove $computerDN.DistinguishedName
}
Close-ExcelFile -fileNameClose $reportRemovedComputerAccounts

} #End function Remove-ComputerAccounts

# ---------------------------|End Of Functions|----------------------------------

    switch ($PsCmdlet.ParameterSetName) 
    { 
    "DisableComputer" { Disable-ComputerAccounts $disableForInactiveDays $move $ouName } 
    "RemoveComputer"  { Remove-ComputerAccounts $removeForDisabledDays } 
    }

The script implements the WhatIf parameter by setting SupportsShouldProcess to true inside the parentheses of the cmdletbinding attribute. One should always run this script for the first time as shown in this example:

DisableOrRemove-StaleComputerAccounts.ps1 -disableForInactiveDays 90 -move -ouName “Stale-Accounts” –WhatIf

Disable inactive accounts and optionally quarantine

The default option is to disable computer accounts and this script is looking for three attributes: PasswordLastSet, LastLogonDate, and Enabled. See the section of the script below:

$DaysInactive = $numberOfDaysInactive  
$time = (Get-Date).Adddays(-($DaysInactive))
$computersOU = "OU=Computers,OU=AlexFirstLocation,DC=ALEXTEST,DC=LOCAL" 
$listOfComputers = (Get-ADComputer -Filter {PasswordLastSet -lt $time -and `
 LastLogonDate -lt $time -and Enabled -eq $true} -SearchBase $computersOU -Properties * | Sort LastLogonDate ) 

Once the collection of all inactive computers has been created, the script, depending on the parameters submitted will first create an Excel File to document this collection, disable accounts, and optionally move stale accounts into the specified OU. For all the accounts being disabled and / or moved, the script will update the appropriate columns in the Excel file, see the picture below.

Disable-MoveComputerAccounts

To produce the above report in my test environment I’ve ran this script as follows:

DisableOrRemove-StaleComputerAccounts.ps1 -disableForInactiveDays 90 -move -ouName “Stale-Accounts”

Please note that the description attribute of all disabled accounts will be updated by adding suffix: $computer.Description += ” – Disabled $numberOfDaysInactive Days Inactive”

Remove disabled accounts

If you want to remove disabled computer accounts, the script will look into the stale accounts container and filter out only the computers that are disabled and with LastLogonDate attribute older that the specified number of days. See the section of the script below:

DaysDisabled = $numberOfDaysDisabled  
$time = (Get-Date).Adddays(-($DaysDisabled))
$listOfDisabledComputers = (Search-ADAccount -AccountDisabled -ComputersOnly -SearchBase $staleAccountsOU | 
Select-Object Name, Enabled, LastLogonDate, DistinguishedName | 
Where {$_.LastLogonDate -lt $time} |
Sort LastLogonDate )

An Excel file will be created and for all the accounts being removed, this script will update the appropriate column in the Excel file, see the picture below.

RemoveComputerAccounts

To produce this report and remove computers, I’ve type the following line:

DisableOrRemove-StaleComputerAccounts.ps1 -removeForDisabledDays 90

As with the rest of the scripts, you can download this script form the download section, under PowerShell folder / DisableOrRemove-StaleAccounts.





Please note: Although the author has made every reasonable attempt to achieve complete accuracy of the content, he assumes no responsibility for errors or omissions. Also, you should use this information as you see fit, and at your own risk.