1

A while ago I've posted a question about exporting specific users information from AD to .CSV file (here). Thanks to the help of Santiago Squarzon I managed to make it work and filter just the way I want, except for one small detail - it takes the script 13 hours to finish executing. I modified some filters, removed one if statement and reduced that time to around 8.5h, but it's still unacceptable for me.

Short summary: I want to export all (enabled) AD users with employee number attribute present. If it's not present, then to check other attribute and so on, total of 5 nested if statements. Then export it to a .CSV file.

Could you take a look at below part of the code and help optimizing it? I believe there is around 150k user accounts to check.

$name = Read-Host -Prompt "Please enter the name for output file."
$filename = $name + ".csv"      
$param = @{
    LDAPFilter = "(&(!extensionAttribute9=0)(!employeenumber=svc)(!(useraccountcontrol:1.2.840.113556.1.4.803:=2)))"
    ResultPageSize = 500
    Properties = @(
        'businesscategory'
        'extensionAttribute4'
        'extensionAttribute9'
        'extensionAttribute13'
        'employeenumber'
    ) 
}
'DOMAIN1','DOMAIN2','DOMAIN3','DOMAIN4' | ForEach-Object {
    $param['Server'] = $_
    foreach($user in Get-ADUser @param) {
                if($user.EmployeeNumber -ne $null){
            [pscustomobject]@{
                Name                 = $user.Name
                SamAccountName       = $user.SamAccountName
                UserPrincipalName    = $user.UserPrincipalName
                BusinessCategory     = $user.businesscategory  -join ", "
                extensionAttribute4  = $user.extensionAttribute4 -join ", "
                extensionAttribute9  = $user.extensionAttribute9 -join ", "
                extensionAttribute13 = $user.extensionAttribute13 -join ", "
                DistinguishedName    = $user.DistinguishedName
                employeenumber       = $user.employeenumber
                Enabled              = $user.Enabled
                Domain               = $_ # Adding the Domain of this user here
        }} else {
            if($user.businesscategory -ne $null) {
                [pscustomobject]@{
                    Name                 = $user.Name
                    SamAccountName       = $user.SamAccountName
                    UserPrincipalName    = $user.UserPrincipalName
                    BusinessCategory     = $user.businesscategory  -join ", "
                    extensionAttribute4  = $user.extensionAttribute4 -join ", "
                    extensionAttribute9  = $user.extensionAttribute9 -join ", "
                    extensionAttribute13 = $user.extensionAttribute13 -join ", "
                    DistinguishedName    = $user.DistinguishedName
                    employeenumber       = $user.employeenumber
                    Enabled              = $user.Enabled
                    Domain               = $_ 
                }} else {
                    if($user.extensionAttribute4 -ne $null){
                        [pscustomobject]@{
                            Name                 = $user.Name
                            SamAccountName       = $user.SamAccountName
                            UserPrincipalName    = $user.UserPrincipalName
                            BusinessCategory     = $user.businesscategory  -join ", "
                            extensionAttribute4  = $user.extensionAttribute4 -join ", "
                            extensionAttribute9  = $user.extensionAttribute9 -join ", "
                            extensionAttribute13 = $user.extensionAttribute13 -join ", "
                            DistinguishedName    = $user.DistinguishedName
                            employeenumber       = $user.employeenumber
                            Enabled              = $user.Enabled
                            Domain               = $_
                    }} else {
                        if($user.extensionAttribute9 -ne $null){
                            [pscustomobject]@{
                                Name                 = $user.Name
                                SamAccountName       = $user.SamAccountName
                                UserPrincipalName    = $user.UserPrincipalName
                                BusinessCategory     = $user.businesscategory  -join ", "
                                extensionAttribute4  = $user.extensionAttribute4 -join ", "
                                extensionAttribute9  = $user.extensionAttribute9 -join ", "
                                extensionAttribute13 = $user.extensionAttribute13 -join ", "
                                DistinguishedName    = $user.DistinguishedName
                                employeenumber       = $user.employeenumber
                                Enabled              = $user.Enabled
                                Domain               = $_
                        }} else {
                            if($user.extensionAttribute13 -ne $null){
                                [pscustomobject]@{
                                    Name                 = $user.Name
                                    SamAccountName       = $user.SamAccountName
                                    UserPrincipalName    = $user.UserPrincipalName
                                    BusinessCategory     = $user.businesscategory  -join ", "
                                    extensionAttribute4  = $user.extensionAttribute4 -join ", "
                                    extensionAttribute9  = $user.extensionAttribute9 -join ", "
                                    extensionAttribute13 = $user.extensionAttribute13 -join ", "
                                    DistinguishedName    = $user.DistinguishedName
                                    employeenumber       = $user.employeenumber
                                    Enabled              = $user.Enabled
                                    Domain               = $_
                            }} else {
                                if($user.SamAccountName -like "*_A*"){
                                    [pscustomobject]@{
                                        Name                 = $user.Name
                                        SamAccountName       = $user.SamAccountName
                                        UserPrincipalName    = $user.UserPrincipalName
                                        BusinessCategory     = $user.businesscategory  -join ", "
                                        extensionAttribute4  = $user.extensionAttribute4 -join ", "
                                        extensionAttribute9  = $user.extensionAttribute9 -join ", "
                                        extensionAttribute13 = $user.extensionAttribute13 -join ", "
                                        DistinguishedName    = $user.DistinguishedName
                                        employeenumber       = $user.employeenumber
                                        Enabled              = $user.Enabled
                                        Domain               = $_
                                }}
                                }
                            }
                        }
                    }
                }
                }
} | Export-Csv "$env:userprofile\Documents\$filename" -Delimiter ';' -NoTypeInformation

'DOMAIN1','DOMAIN2','DOMAIN3','DOMAIN4' are (I believe) 4 different sub-domains in one domain tree: domain1.test.com, domain2.test.com etc.

shalan
  • 25
  • 5
  • You need to add what's happening in the `else` condition to understand if that can be improved. Aside from that, the next step on improving the runtime of this is multithreading imo (processing all Domains in parallel). Also, `(!employeenumber=svc)` if you are looking for `employeenumber` __not like__ `SVC` remember to use wildcards => `(!employeenumber=*svc*)` – Santiago Squarzon May 07 '22 at 18:09
  • ```else``` conditions contain similar ```if```: ```else { if($user.businesscategory -ne $null) { (...) ``` and so on, total of 5 conditions. I will look into the multithreading too. – shalan May 07 '22 at 18:43
  • Add all conditions to your code, there might be something that can be improved there – Santiago Squarzon May 07 '22 at 18:45
  • 1
    I've edited my question and added rest of the code. Sorry for terrible formatting though. – shalan May 07 '22 at 19:13
  • You're always outputting the same object on each of your conditional statements, and by the looks of it, your conditional statement could be just one, using something like: __"output this object if employeeNumber is NOT NULL OR businesscategory is NOT NULL OR extensionAttribute4 is NOT NULL..."__ and so on, and if my assumption is right, then this condition could be removed and use an LDAP Filter instead which would improve the runtime big time – Santiago Squarzon May 07 '22 at 19:23
  • 1
    Can't see much low hanging fruit for easy speedups. Run `measure-command {...}` around your `get-aduser ...` to get its runtime; could it be connecting to a slow domain controller and might be faster with `-Server ...` to talk to a closer/faster DC? Is one of the domains much slower? Maybe the LDAP filter is very slow, could be quicker to pull all results and filter locally? Then query the four domains all at once with Jobs or Runspaces but it's not a trivial change. After that, look for `[ADSISearcher]` instead of `Get-ADUser` but that's an even bigger change. – TessellatingHeckler May 07 '22 at 19:33
  • ```"output this object if employeeNumber is NOT NULL OR businesscategory is NOT NULL OR extensionAttribute4 is NOT NULL..."``` problem with this is that some accounts with null employeeNumber also have to be imported, that's why i'm using ```if``` statement. So it's something like "output this object if X is not null, otherwise check Y and if it's not null, output the object, otherwise check Z..." – shalan May 07 '22 at 19:42
  • 1
    @shalan see my temporary answer, hopefully it helps you understand what I mean. And these `or` conditions can also be replaced with an LDAP Filter. – Santiago Squarzon May 07 '22 at 20:22
  • one hing you can probably do to increase your performance is to translate your if conditions to an LDAP filter. Depending on how many accounts your conditions apply to this could give you a very big boost. Another thing you should consider is querying your domains in parallel instead of sequentially. With Powershell 7+ you can just add the -Parallel flag to your ForEach-Object to do that. Assuming you are not limited by local compute or network resources that should give you a massive improvement. – Paul May 07 '22 at 20:31

1 Answers1

1

Not meant as an answer but to prove a point, will delete after.

$user = [pscustomobject]@{
    SamAccountName = '_A'
    EmployeeNumber = $null
    businesscategory = $null
    extensionAttribute4 = $null
    extensionAttribute9 = $null
    extensionAttribute13 = $null
}

# your code
if($user.EmployeeNumber -ne $null){ $user }
else { if($user.businesscategory -ne $null) { $user }
else { if($user.extensionAttribute4 -ne $null){ $user }
else { if($user.extensionAttribute9 -ne $null){ $user }
else { if($user.extensionAttribute13 -ne $null){ $user }
else { if($user.SamAccountName -like "*_A*"){ $user }}}}}}

# can be reduced to one condition
# (Not implying this is faster)
if(
    $user.EmployeeNumber -or
    $user.businesscategory -or
    $user.extensionAttribute4 -or
    $user.extensionAttribute9 -or
    $user.extensionAttribute13 -or
    $user.SamAccountName -like "*_A*"
) {
    $user
}

# but above can be translated to the following LDAP Filter, which is faster
(|
    (EmployeeNumber=*)
    (businesscategory=*)
    (extensionAttribute4=*)
    (extensionAttribute9=*)
    (extensionAttribute13=*)
    (SamAccountName=*_A*)
)
Santiago Squarzon
  • 41,465
  • 5
  • 14
  • 37