Search code examples
powershellactive-directoryget-aduser

System.OutOfMemoryException when running Get-Aduser on large data sets


The below script is considerably cut down in terms of the number of user properties being requested and there are approximately 50,000 users to iterate through. My previous approach was to first get a collection of allusers then do a get-aduser on each one for the information, however that approach was taking a long time (up to 16 hours). With the approach in the script below where the get-aduser is only run once the script flies through doing approximately 20,000 users in 11 minutes. However at that stage you can suddenly see it slow down and eventually crashes out with the error pasted at the end of the code. Is there a way around this issue?

$csv = "Computers-{0:dd-MM-yyyy_HHmm}.csv" -f (get-date)
$UsrProps = "SamAccountName",
"AccountExpirationDate",
"accountExpires",
"AccountLockoutTime",
"BadLogonCount",
"badPwdCount",
"badPasswordTime",
"SamAccountName"


Get-ADUser -Filter *  -Properties $UsrProps -server $domain |
ForEach-Object {

    $hash = @{
        AccountExpirationDate = $_.AccountExpirationDate
        AccountLockoutTime    = $_.AccountLockoutTime
        accountExpires        = $_.accountExpires
        BadLogonCount         = $_.BadLogonCount
        badPwdCount           = $_.badPwdCount
        badPasswordTime       = $_.badPasswordTime
        SamAccountName        = $_.SamAccountName
    }
       
    $PSCustObj = [pscustomobject]$hash
    $results = $PSCustObj
    $results |
    select-object @{ l = "SamAccountName"; e = { [string]$_.SamAccountName } },
    @{ l = "AccountExpirationDate"; e = { [string]$_.AccountExpirationDate } },
    @{ l = "AccountLockoutTime"; e = { [string]$_.AccountLockoutTime } },
    @{ l = "BadLogonCount"; e = { [string]$_.BadLogonCount } },
    @{ l = "badPwdCount"; e = { [string]$_.badPwdCount } },
    @{ N = 'badPasswordTime'; E = { [DateTime]::FromFileTime($_.badPasswordTime) } }  | 
    export-csv "$PWD\Logs\$domain\Users\$csv" -noTypeInformation -Append
    
}


Get-ADUser : Exception of type 'System.OutOfMemoryException' was thrown. At line:231 char:5
+     Get-ADUser -Filter *  -Properties $UsrProps -server $domain |
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Get-ADUser], OutOfMemoryException
    + FullyQualifiedErrorId : ActiveDirectoryCmdlet:System.OutOfMemoryException,Microsoft.ActiveDirectory.Management.Commands.GetADUser

Solution

  • You can greatly streamline your command, which may fix the out-of-memory problem (it definitely reduces overall memory consumption and speeds up your command):

    Get-ADUser -Filter * -Properties $UsrProps -server $domain |
      Select-Object @{ l = 'SamAccountName'; e = { [string]$_.SamAccountName } },
        @{ l = 'AccountExpirationDate'; e = { [string]$_.AccountExpirationDate } },
        @{ l = 'AccountLockoutTime'; e = { [string]$_.AccountLockoutTime } },
        @{ l = 'BadLogonCount'; e = { [string]$_.BadLogonCount } },
        @{ l = 'badPwdCount'; e = { [string]$_.badPwdCount } },
        @{ N = 'badPasswordTime'; E = { [DateTime]::FromFileTime($_.badPasswordTime) } } | 
      Export-Csv "$PWD\Logs\$domain\Users\$csv" -NoTypeInformation
    

    Presumably, memory pressure can still build due to deferred garbage collection, so the following variation may be necessary, which runs the .NET garbage periodically, after processing every batch of N users (tweak as needed):

    $i = 0
    Get-ADUser -Filter * -Properties $UsrProps -server $domain |
      ForEach-Object {
    
        # Run the garbage collector after processing N users.
        if (++$i % 1000 -eq 0) { [GC]::Collect(); [GC]::WaitForPendingFinalizers() }
    
        # Construct and output the output object for this user.
        [pscustomobject] @{
          SamAccountName        = [string]$_.SamAccountName
          AccountExpirationDate = [string]$_.AccountExpirationDate
          AccountLockoutTime    = [string]$_.AccountLockoutTime
          BadLogonCount         = [string]$_.BadLogonCount
          badPwdCount           = [string]$_.badPwdCount
          badPasswordTime       = [DateTime]::FromFileTime($_.badPasswordTime)
        }
    
      } |
      Export-Csv "$PWD\Logs\$domain\Users\$csv" -NoTypeInformation