In this post we will be implementing two PowerShell scripts to automatically clean up our Active Directory of old, unused computer objects.

The first script will scan the Active Directory for computer objects that are no longer used, disable them and put them in a seperate OU. It also modifies some extensionAttributes on the object, in case we need to restore them to the original location.

The second script will check the OU with the disabled computer objects, and if a certain amount of time has passed, it will delete them.

Before we begin

The script below assumes that
– The domain FQDN is “company.com”
– You created the following OU: “company.com/computers/MarkedForRemoval”
– ExtensionAttribute14 and ExtentionAttribute15 are not being used. Otherwise change this in the script.

The script can still be simplified a lot more, more currently it serves it purpose.
The Start-Transcript and Stop-Transcript allows us to easily create some sort of logging.
Creating this as a function with parameters is on my TODO list

Script 1 : Cleanup-MarkComputersForRemoval.ps1

#Create a log file of the current run...
#Change the directory to whatever you want to use.
Start-Transcript -Force "C:\Scripts\Logs\Cleanup-MarkComputersForRemoval.txt"

#Load the Active Directory module if not yet loaded.
#Make sure you run this on a system that has the AD powershell module installed (RSAT)
If (!(Get-module ActiveDirectory)) 
{
    Write-Output "Loading Module..."
    Import-Module ActiveDirectory
}

#Set the timespan.
#Any computer object (with the exception of servers) will be removed if they've been inactive for at least this amount of time.
$timeSpan = New-TimeSpan -Days 90

#Array to hold newly added marked computers.
$newMarkedComputers = @();

#testmode toggle.
#Set this to true to test the script. It will only generate the transcript, but not remove any objects.
$testMode = $false;

#Find all inactive systems, based on the timespan.
$inactiveComputers = Search-ADAccount -ComputersOnly -AccountInactive -TimeSpan $timeSpan;
$inactiveComputers = $inactiveComputers | Get-ADComputer -Properties Name, OperatingSystem,DNSHostName,DistinguishedName | Select Name, OperatingSystem, DNSHostName, DistinguishedName

#Filter out Windows Servers, remove this line in case you also want to auto remove servers.
$inactiveComputers = $inactiveComputers | Where-Object {($_.OperatingSystem -notlike "*Windows Server*")}

#Make sure our list only contains windows clients. Servers are already filtered out by the line above.
$inactiveComputers = $inactiveComputers | Where-Object {($_.OperatingSystem -like "*Windows*")}

#Go over every inactive computer we found.
foreach($inactiveComputer in $inactiveComputers)
{
    #If it's already in the markedforremoval OU, we can safely ignore it.
    #Note: We could implement this part during the search.
    if($inactiveComputer.DistinguishedName -like "*OU=MarkedForRemoval*")
    {
        Write-Host "[SKIPPED] $($inactiveComputer.Name) is already marked for removal."    
        continue;
    }
    
    #The testing mode only logs to the transcript file.
    if($testMode -eq $true)
    {
        Write-Host "[TESTMODE] $($inactiveComputer.Name) will be marked for removal.";
    }

    #If we are not in testing mode
    else
    {
        Write-Host "[MARKING] $($inactiveComputer.Name) will be marked for removal.";
        #Set attribute15 to the old OU (in case we need to recover the object to the original location.
        Set-ADComputer $inactiveComputer.Name -Replace @{ extensionAttribute15  =  $inactiveComputer.DistinguishedName  }
        #Set attribute14 to the date this object was moved and disabled (in case we need to do some additional checks)
        Set-ADComputer $inactiveComputer.Name -Replace @{ extensionAttribute14  =  (Get-Date -Format u).ToString() }
        #Disable the object.
        Disable-ADAccount -Identity $inactiveComputer.DistinguishedName
        #Move to the MarkedForRemoval OU.
        Move-ADObject -Identity $inactiveComputer.DistinguishedName -TargetPath "OU=MarkedForRemoval,OU=Computers,DC=COMPANY,DC=COM"
    }
    
    $newMarkedComputers += $inactiveComputer
}

#Create a little summary at the end.
Write-Host "Total new inactive computers (+ $($timeSpan.Days) days) : " $newMarkedComputers.Count

#Dump all the computers of the latest run into a CSV file.
#NOTE: You could add a timestamp to the filename, to create seperate files for each run.
$newMarkedComputers | Select-Object Name, OperatingSystem | Export-CSV -Append -UseCulture -NoTypeInformation "C:\Scripts\Exports\New-InactiveComputerAccounts.csv"

Stop-Transcript

Script 2 : Cleanup-DeleteMarkedComputers.ps1

#Create a log file of the current run...
#Change the directory to whatever you want to use.
Start-Transcript -Force "C:\Scripts\Logs\Cleanup-DeleteMarkedComputers.txt";

#Load the Active Directory module if not yet loaded.
#Make sure you run this on a system that has the AD powershell module installed (RSAT)
If (!(Get-module ActiveDirectory)) 
{
    Write-Output "Loading Module..."
    Import-Module ActiveDirectory
}

#if a computer object, in the MarkedForRemoval OU, is older than $maxage, it will be removed.
$maxage = 183;

#Get all the computers in the MarkedForRemoval OU.
$markedComputers = Get-ADComputer -Filter * -Properties * -SearchBase "OU=MarkedForRemoval,OU=Computers,DC=COMPANY,DC=COM"
foreach($markedComputer in $markedComputers)
{
    #Add some logging text.
    Write-Host "[INFO] Processing computer : $($markedComputer.Name)";
    #Create a timespan on ExtensionAttribute14 (gets populated in the first script with a timestamp).
    $timespan = New-TimeSpan -Start ([DateTime]($markedComputer.extensionAttribute14).ToString()) -End (Get-Date)
    #If the object is older than maxage...
    if($timespan.TotalDays -gt $maxage)
    { 
        #Remove it
        Write-Host "[DELETE] $((Get-Date).ToString()) - Deleting computer: $($markedComputer.Name) - Disabled Age = $($timespan.TotalDays) days.";
        Remove-ADObject -Identity $markedComputer.DistinguishedName -Confirm:$False

    }
    else
    {
        #Otherwise just add some logging information.
        Write-Host "[SKIPPED] $((Get-Date).ToString()) Skipping computer: $($markedComputer.Name) - Disabled Age = $($timespan.TotalDays) days.";
    }
}

Stop-Transcript



Finally…

You can use a Task Schedule to automatically run these scripts on a daily base.

Leave a Reply

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