25 January 2016

PowerShell speed optimizing on Active Directory cmdlets

Lets imagine, you have a task to find some AD user accounts based on a criteria and perform a simple task for each of them. My case is to clear manager for all user accounts, where extensionattribute3 is not present. This needs to be done regularly. So the time spent on this task should be minimized.

Initial version took about 160 seconds to run:

Measure-Command {
$users = Get-ADUser -Filter * -Properties extensionAttribute3 | ? extensionAttribute3 -ne 'Nortal Employee'
$users | foreach {Set-ADUser $_.SamAccountName -Clear manager -WhatIf}
}

Lets try to eliminate users, who already have manager cleared and not clearing the manager for them. This run took about 20 seconds because, instead of thousands of users, only few hundred were actually touched:

Measure-Command {
$users = Get-ADUser -Filter * -Properties manager,extensionAttribute3 | ? extensionAttribute3 -ne 'Nortal Employee' | ? manager
$users | foreach {Set-ADUser $_.SamAccountName -Clear manager -WhatIf}
}

Next iteration would be piping all users directly to Set-ADUser cmdlet, but this would not work straight forward, we will need Select-Object with ExpandProperty. This run took about 5 seconds because Set-ADUser was loaded only once instead of hundred times:

Measure-Command {
$users = Get-ADUser -Filter * -Properties manager,extensionAttribute3 | ? extensionAttribute3 -ne 'Nortal Employee' | ? manager
$users | select -ExpandProperty SamAccountName | Set-ADUser -Clear manager -WhatIf
}

Get-ADUser will still get all users and the filtration is done in PowerShell. This can be optimized to pass LDAP query to AD to return only needed user accounts. This run took about 2 seconds:

Measure-Command {
$users = Get-ADUser -LDAPFilter "(&(manager=*)(!extensionAttribute3=*))"
$users | select -ExpandProperty SamAccountName | Set-ADUser -Clear manager -WhatIf
}

This command can be converted to oneliner. This will have no noticeable effect on command duration, but looks perhaps cleaner:

Measure-Command {Get-ADUser -LDAPFilter "(&(manager=*)(!extensionAttribute3=*))" | select -ExpandProperty SamAccountName | Set-ADUser -Clear manager -WhatIf}

So our initial code was later optimized to to run nearly 100 times faster. Not a bad thing for a scheduled task running daily or hourly.