0

New to powershell, learning it on the fly. I run a report weekly that requires me to get a list of names from my employer, specifically the first half of the userprincipalname attribute, check each associated account in AD, get account details, and save those details to a CSV that I later input into Excel.

My list is usually 5000 names long, and all the users are scattered in different OU's at different levels. What I originally used was the code below, but by narrowing my OU's down to my local office (OU=Level_1), I noticed it was fast, but only found users that are in Level 1 (about 80%) of the users, and omitted everyone else. I stopped specifying Level 1 and started at Level 2, and it ran much slower but found a few more. Starting at Level 3 found everyone, but takes now 4-5 seconds per $user in $users... several hours overall.

I ::think:: there is a way to make this faster by specifying if/else statements:

  For every $user in $users, look in the Level_1 OU
    if User is found, append Employee_Details.CSV, move to next $user
    else look in the Level_2 OU
        if User is found, append Employee_Details.CSV, move to next $user
        else look in the Level_3 OU
          if User is found, append Employee_Details.CSV, move to next $user
          else look in the Level_4 OU
            if User is not found, write-out "User Not Found"

EDIT: Josh below had a different way to do the same thing:

  For every $user in $users, look in the Level_1 OU
    if User is found, append Employee_Details.CSV
    else send the user to a new array $Level_2.
  For every $user in $Level_2, look in the Level_2 OU
    if User is found, append Employee_Details.CSV,
    else send the user to a new array $Level_3.
  For every $user in $Level_3, look in the Level_2 OU
    if User is found, append Employee_Details.CSV,
    else send the user to a new array $Level_4.

Seem's to be a great way to do it so far, but not all the way there yet.

$users = (Import-csv C:\Users\mickey.mouse\Desktop\userprincipalname_List.txt).userprincipalname
$count = 0
$start = Get-Date

foreach ($user in $users) {
    $count++
    
    # calculate percent complete
    $percentComplete = ($count / $users.Count) * 100

    # Define parameters for Write-Progress
    $progressParameters = @{
        Activity = "Searching AD for userprincipalnames on list [$($count)/$($users.Count)] $($secondsElapsed.ToString('hh\:mm\:ss'))"
        Status = 'Processing'
        CurrentOperation = "Writing AD data for: $user to file"
        PercentComplete = $percentComplete
    }

    # if we have an estimate for the time remaining, add it to the Write-Progress parameters
    if ($secondsRemaining) {
        $progressParameters.SecondsRemaining = $secondsRemaining
    }

    # Write the progress bar
    Write-Progress @progressParameters

    # Insert code to be performed in each iteration of the array here
    Get-ADUser -searchbase "OU=Level_1,OU=Level_2,OU=Level_3,OU=Level_4,DC=Alpha,DC=Bravo,DC=Charlie" -Filter "userprincipalname -like '*$user*'" -properties * | select-object employeeid, name, title, mail, description, lastlogondate, userprincipalname | 
    Export-CSV C:\Users\mickey.mouse\Desktop\Employee_Details.csv -NoTypeInformation -append


    # estimate the time remaining
    $secondsElapsed = (Get-Date) – $start
    $secondsRemaining = ($secondsElapsed.TotalSeconds / $count) * ($users.Count – $count)
}

1 Answers1

0

So I was trying to think of how to do this, and I think the best way would be trying to search for all AD users against the Level 1 OU. If the user isn't in that OU, then add them to an array that contains all users not in the Level 1 OU.

At which point you would run another ForEach loop to try the Level 2 OU, then Level 3, and so on.

I modified your script to do this for Level 1 and Level 2. I haven't tested it, so let me know if this helps, or if you have any questions or issues running it:

$Users = (Import-csv C:\Users\mickey.mouse\Desktop\userprincipalname_List.txt).userprincipalname
$Count = 0
$Start = Get-Date

foreach ($User in $Users) {
    $Count++
    $PercentComplete = ($Count / $Users.Count) * 100
    $ProgressParameters = @{
        # Modified Activity to reflect changes
        Activity = "Searching Level 1 OU for userprincipalnames on list [$($count)/$($users.Count)] $($secondsElapsed.ToString('hh\:mm\:ss'))"
        Status = 'Processing'
        CurrentOperation = "Writing AD data for: $user to file"
        PercentComplete = $PercentComplete
    }
    if ($SecondsRemaining) {
        $ProgressParameters.SecondsRemaining = $SecondsRemaining
    }
    Write-Progress @ProgressParameters

    # Create a variable to catch Users not in Level_1_OU
    $Level_1_Catch = @()
    
    # Clear Level_1_User variable
    $Level_1_User = $null

    # Try to perform Get-ADUser, if not found SilentlyContinue
    $Level_1_User = Get-ADUser -searchbase "OU=Level_1,OU=Level_2,OU=Level_3,OU=Level_4,DC=Alpha,DC=Bravo,DC=Charlie" -Filter "userprincipalname -like '*$user*'" -properties * -ErrorAction SilentlyContinue | select-object employeeid, name, title, mail, description, lastlogondate, userprincipalname 
    
    # If user is detected in $Level_1_OU, export to csv
    If ($Level_1_User -ne $null){
        $Level_1_User | Export-CSV C:\Users\mickey.mouse\Desktop\Employee_Details.csv -NoTypeInformation -append
    }

    # If not, add the user to the $Level_1_Catch
    Else{
     $Level_1_Catch += $User    
    }
    $SecondsElapsed = (Get-Date) – $Start
    $SecondsRemaining = ($secondsElapsed.TotalSeconds / $Count) * ($Users.Count – $Count)
}

$Count = 0
foreach ($User in $Level_1_Catch) {
    $Count++
    $PercentComplete = ($Count / $Users.Count) * 100
    $ProgressParameters = @{
        # Modified Activity to reflect changes
        Activity = "Searching Level 2 OU for userprincipalnames on list [$($count)/$($users.Count)] $($secondsElapsed.ToString('hh\:mm\:ss'))"
        Status = 'Processing'
        CurrentOperation = "Writing AD data for: $user to file"
        PercentComplete = $PercentComplete
    }
    if ($SecondsRemaining) {
        $ProgressParameters.SecondsRemaining = $SecondsRemaining
    }
    Write-Progress @ProgressParameters

    # Create a variable to catch Users not in Level_2_OU
    $Level_2_Catch = @()
    
    # Clear Level_2_User variable
    $Level_2_User = $null

    # Try to perform Get-ADUser, if not found SilentlyContinue
    $Level_2_User = Get-ADUser -searchbase "OU=Level_2,OU=Level_3,OU=Level_4,DC=Alpha,DC=Bravo,DC=Charlie" -Filter "userprincipalname -like '*$user*'" -properties * -ErrorAction SilentlyContinue | select-object employeeid, name, title, mail, description, lastlogondate, userprincipalname 
    
    # If user is detected in $Level_1_OU, export to csv
    If ($Level_2_User -ne $null){
        $Level_2_User | Export-CSV C:\Users\mickey.mouse\Desktop\Employee_Details.csv -NoTypeInformation -append
    }

    # If not, add the user to the $Level_2_Catch
    Else{
     $Level_2_Catch += $User
    }
    $SecondsElapsed = (Get-Date) – $Start
    $SecondsRemaining = ($secondsElapsed.TotalSeconds / $Count) * ($Users.Count – $Count)
}

Edit: So the issue you were seeing below was that I put the line to make the user catch array (i.e.: $Level_2_Catch = @()) inside of the ForEach, which was clearing it each time it would loop through.

I just tried this in a test environment and it worked as intended, let me know if it works for you as well:

$Users = (Import-csv C:\Users\mickey.mouse\Desktop\userprincipalname_List.txt).userprincipalname
$Count = 0
$Start = Get-Date

# Create an array to catch Users not in Level_1_OU
## This needs to be outside of the ForEach loop, otherwise it clears the array each time it runs.
$Level_1_Catch = @()

foreach ($User in $Users) {
    $Count++
    $PercentComplete = ($Count / $Users.Count) * 100
    $ProgressParameters = @{
        # Modified Activity to reflect changes
        Activity = "Searching Level 1 OU for userprincipalnames on list [$($count)/$($users.Count)] $($secondsElapsed.ToString('hh\:mm\:ss'))"
        Status = 'Processing'
        CurrentOperation = "Writing AD data for: $user to file"
        PercentComplete = $PercentComplete
    }
    if ($SecondsRemaining) {
        $ProgressParameters.SecondsRemaining = $SecondsRemaining
    }
    Write-Progress @ProgressParameters

    # Clear Level_1_User variable
    $Level_1_User = $null

    # Try to perform Get-ADUser, if not found SilentlyContinue
    $Level_1_User = Get-ADUser -searchbase "OU=Level_1,OU=Level_2,OU=Level_3,OU=Level_4,DC=Alpha,DC=Bravo,DC=Charlie" -Filter "userprincipalname -like '*$user*'" -properties * -ErrorAction SilentlyContinue | select-object employeeid, name, title, mail, description, lastlogondate, userprincipalname 
    
    # If user is detected in $Level_1_OU, export to csv
    If ($Level_1_User -ne $null){
        $Level_1_User | Export-CSV C:\Users\mickey.mouse\Desktop\Employee_Details.csv -NoTypeInformation -append
    }

    # If not, add the user to the $Level_1_Catch
    Else{
     $Level_1_Catch += $User    
    }
    $SecondsElapsed = (Get-Date) – $Start
    $SecondsRemaining = ($secondsElapsed.TotalSeconds / $Count) * ($Users.Count – $Count)
}

$Count = 0
# Create an array to catch Users not in Level_2_OU
## This needs to be outside of the ForEach loop, otherwise it clears the array each time it runs.
$Level_2_Catch = @()
foreach ($User in $Level_1_Catch) {
    $Count++
    $PercentComplete = ($Count / $Users.Count) * 100
    $ProgressParameters = @{
        # Modified Activity to reflect changes
        Activity = "Searching Level 2 OU for userprincipalnames on list [$($count)/$($users.Count)] $($secondsElapsed.ToString('hh\:mm\:ss'))"
        Status = 'Processing'
        CurrentOperation = "Writing AD data for: $user to file"
        PercentComplete = $PercentComplete
    }
    if ($SecondsRemaining) {
        $ProgressParameters.SecondsRemaining = $SecondsRemaining
    }
    Write-Progress @ProgressParameters
    
    # Clear Level_2_User variable
    $Level_2_User = $null

    # Try to perform Get-ADUser, if not found SilentlyContinue
    $Level_2_User = Get-ADUser -searchbase "OU=Level_2,OU=Level_3,OU=Level_4,DC=Alpha,DC=Bravo,DC=Charlie" -Filter "userprincipalname -like '*$user*'" -properties * -ErrorAction SilentlyContinue | select-object employeeid, name, title, mail, description, lastlogondate, userprincipalname 
    
    # If user is detected in $Level_1_OU, export to csv
    If ($Level_2_User -ne $null){
        $Level_2_User | Export-CSV C:\Users\mickey.mouse\Desktop\Employee_Details.csv -NoTypeInformation -append
    }

    # If not, add the user to the $Level_2_Catch
    Else{
     $Level_2_Catch += $User
    }
    $SecondsElapsed = (Get-Date) – $Start
    $SecondsRemaining = ($secondsElapsed.TotalSeconds / $Count) * ($Users.Count – $Count)
}
  • This ::almost:: worked. It iterated through Level_1, but only caches the very last userprincipalname on the list to $Level_1_Catch. If that user is in Level 2, it'll find it. If not, returns nothing. I played around with the file, adding fake information as the first userprincipalname, a level 2 name as the second entry, then bunch of level 1 names, and ended with a level 2. In theory, the first two names should have been pushed to the $Level_1_Catch array, the rest would populate the Employee_Details.csv, then the last name would be added to the $Level_1_Catch array. – Always_Learning Dec 06 '21 at 15:57
  • In practice, did not work. Only the last name is sent to the $Level_1_Catch array. I let it run, interrupted it halfway through, then checked the $Level_1_Catch thinking that maybe it's just appending each new name. If it was, the second name on the list should be on the array, because it hasn't reached the last one yet. The $Level_1_Catch was empty, so it seems like nothing is being sent to it until the very end. I'm still learning, but I think the issue may be in the part below: ``` # If not, add the user to the $Level_1_Catch Else{ $Level_1_Catch += $User } ``` – Always_Learning Dec 06 '21 at 15:57
  • @Always_Learning - I'll spin up a test environment and get back to you in a bit. I think I can probably tweak it to get you there. – Josh Gattis Dec 06 '21 at 19:36
  • @Always_Learning - I just posted an edit, the issue was having the $Level_#_Catch array inside of the for loop. I ran the script I edited and it worked. But let me know if you have any other questions. – Josh Gattis Dec 06 '21 at 20:30