0
votes

I'm trying to make a backup of user attributes for multiple domains. I need to export all users to a csv file for each domain. Get-QADUser has been found to take 72 hours on one domain which is to slow. Instead I am using ``.

For some reason, on several domains it is not picking up all the users and I'm not sure why. Here is my code...

function CreateQueriedCsvDataSheet
{
    $csvFileWithPath = 'C:\Scripts\Tests\testResults.csv'
    $DomainControlConnector = 'www.example.com'
    $DomainName = 'myDomain1'
    $domainUserProperties = @('sAMAccountName', 'msRTCSIP-UserEnabled', 'msRTCSIP-OptionFlags', 'msRTCSIP-PrimaryUserAddress', 'msRTCSIP-PrimaryHomeServer', 
    'mail', 'msExchMasterAccountSid', 'homeMDB', 'proxyaddresses', 'legacyExchangeDN', 
    'lastLogonTimestamp', 'logonCount', 'lastLogoff', 'lastLogon', 'pwdLastSet', 'userAccountControl', 'whenCreated', 'whenChanged', 'accountExpires', 
    'sn', 'givenName', 'displayName', 'distinguishedName', 'initials', 'l', 'st', 'street', 'title', 'description', 'postalCode', 'physicalDeliveryOfficeName', 'telephoneNumber', 'facsimileTelephoneNumber', 'info', 'memberOf', 'co', 'department', 'company', 'streetAddress', 'employeeNumber', 'employeeType', 'objectGUID', 'employeeID', 'homeDirectory', 'homeDrive', 'scriptPath', 'objectSid', 'userPrincipalName', 'url', 'msDS-SourceObjectDN', 'manager', 'extensionattribute8')

    Logger $LogFileWithPath "Querying $DomainName for user account attributes and exporting to csv file $csvFileWithPath..."
    powershell -Command {
        Param ([string]$domainControlConnector, [string]$csvOutFile, [string]$DomainName, [String[]]$domainUserProperties)
        $domain = "LDAP://$domainControlConnector"

        Write-Host "Searching Users Properties for domain $DomainNamein in AD..."
        $adDomain = New-Object System.DirectoryServices.DirectoryEntry($domain)
        $adSearcher = New-Object System.DirectoryServices.DirectorySearcher($adDomain)
        $adSearcher.Filter = '(objectCategory=User)'
        $adSearcher.PageSize=1000
        $adSearcher.PropertiesToLoad.AddRange($domainUserProperties)
        $userRecords = $adSearcher.FindAll()
        Write-Host "Complete"

        # The AD results are converted to an array of hashtables.
        Write-Host "Exporting User Attributes to table..."
        $userPropertiesTable = @()
        foreach($record in $userRecords) {
            $hashUserProperty = @{}
            foreach($userProperty in $domainUserProperties){
                if ($record.Properties[$userProperty]) {
                    $hashUserProperty.$userProperty = $record.Properties[$userProperty][0]
                } else {
                    $hashUserProperty.$userProperty = $null
                }
            }
            $userPropertiesTable += New-Object PSObject -Property $hashUserProperty
        }
        Write-Host "Complete."

        $listOfBadDateValues = '9223372036854775807', '9223372036854770000', '0'
        $maxDateValue = '12/31/1600 5:00 PM'

        Write-Host "fixing table property titles and values for report"
        #$userPropertiesTable[0] = $userPropertiesTable[0] -replace 'givenname','FirstName'

        $tableFixedValues = $userPropertiesTable | % { 
            if ($_.lastLogonTimestamp) {
                $_.lastLogonTimestamp = ([datetime]::FromFileTime($_.lastLogonTimestamp)).ToString('g')
            }; if (($_.AccountExpires) -and ($listOfBadDateValues -contains $_.AccountExpires)) {
                $_.AccountExpires = $null
            } else {
                if (([datetime]::FromFileTime($_.AccountExpires)).ToString('g') -eq $maxDateValue) {
                    $_.AccountExpires = $null
                } Else {
                    $_.AccountExpires = ([datetime]::FromFileTime($_.AccountExpires)).ToString('g')
                }
            }; if (($_.lastLogon) -and ($listOfBadDateValues -contains $_.lastLogon)) {
                $_.lastLogon = $null
            } else {
                if (([datetime]::FromFileTime($_.lastLogon)).ToString('g') -eq $maxDateValue) {
                    $_.lastLogon = $null
                } Else {
                    $_.lastLogon = ([datetime]::FromFileTime($_.lastLogon)).ToString('g')
                }
            }; if (($_.pwdLastSet) -and ($listOfBadDateValues -contains $_.pwdLastSet)) {
                $_.pwdLastSet = $null
            } else {
                if (([datetime]::FromFileTime($_.pwdLastSet)).ToString('g') -eq $maxDateValue) {
                    $_.pwdLastSet = $null
                } Else {
                    $_.pwdLastSet = ([datetime]::FromFileTime($_.pwdLastSet)).ToString('g')
                }
            };$_}
        Write-Host "Complete"

        Write-Host "Exporting table to csv file $csvOutFile"
        $tableFixedValues | Select-Object $domainUserProperties | Export-Csv $csvOutFile -NoTypeInformation -Force
        Write-Host "Complete"
    } -args $DomainControlConnector, $csvFileWithPath, $DomainName, $domainUserProperties
}

Function Main
{
    CreateQueriedCsvDataSheet

}

Main

Note: I also noticed for memberOf attribute, not all the groups are being listed for the user...

Update with TheMadTechnician's answer.

Currently working with code snippet...

    # The AD results are converted to an array of hashtables.
    Write-Host "Exporting User Attributes to table..."
    $userPropertiesTable = @()
    foreach($record in $userRecords) {
        $hashUserProperty = @{}
        foreach($userProperty in $domainUserProperties){
            if (($userProperty -eq 'objectGUID') -or ($userProperty -eq 'objectSid')) {
                if ($record.Properties[$userProperty]) {
                    $hashUserProperty.$userProperty = $record.Properties[$userProperty][0]
                } else {
                $hashUserProperty.$userProperty = $null
                }
            } Else {
                if ($record.Properties[$userProperty]) {
                    $hashUserProperty.$userProperty = ($record.Properties[$userProperty] -join '; ').trim('; ')
                } else {
                $hashUserProperty.$userProperty = $null
                }
            } #end Else
        } #end ForEach
        $userPropertiesTable += New-Object PSObject -Property $hashUserProperty
    } #end ForEach
    Write-Host "Complete."
1
Do you really need all of those properties? That would be part of the performance issue.Matt
He didn't change the SizeLimit, so it should be at it's default of zero, and he did set the PageLimit to 1000. The 52 properties might be the issue, or maybe it's that he gets all the users, then loops through them and builds a hashtable out of each record, then builds an object out of each hashtable. All of this within a nested PowerShell process. He also has so many If/Else and nested If/Else statements that he obviously needs to become familiar with the Switch command. If you want a separate process, use Start-Job.TheMadTechnician
I feel that $hashUserProperty.$userProperty = $record.Properties[$userProperty][0] might be dropping the memberof since you are only calling the one element. Would have to test to be sure. @TheMadTechnician... yeah I saw that.Matt
@TheMadTechnician the switch statement is used in place of doing something based upon 1 variable. I have multiple different if statements to do something off multiple different variables. If I don't set the pagelimit to 1000, it only grabs 1000 accounts. I read that the default pagelimit for this class is not set, so it needs to be set if you want more than 1000 accounts. I've toyed with the amount and got the same all around. I've also tried minimizing the properties to just sAMAccountName, and still only grab the same#/whole# users. Also, when I tried start-job, it was adding columns...Fiddle Freak
@Matt I believe the hash is basically having the hashKey as the value of the user property, and the hashValue as the user property. I got this code from here - blog.schmijos.ch/2013/09/27/… .Feel free to toy around with it.Fiddle Freak

1 Answers

1
votes

Ok, here's my Switch example to clean up your If/Else block.

$tableFixedValues = Switch($userPropertiesTable){ 
            {$_.lastLogonTimestamp} {$_.lastLogonTimestamp = ([datetime]::FromFileTime($_.lastLogonTimestamp)).ToString('g')}
            {($_.AccountExpires) -and ($listOfBadDateValues -contains $_.AccountExpires) -or ([datetime]::FromFileTime($_.AccountExpires)).ToString('g') -eq $maxDateValue} {$_.AccountExpires = $null}
            {$_.AccountExpires} {([datetime]::FromFileTime($_.AccountExpires)).ToString('g')}
            {$listOfBadDateValues -contains $_.lastLogon -or ([datetime]::FromFileTime($_.lastLogon)).ToString('g') -eq $maxDateValue} {$_.lastLogon = $null}
            {$_.lastLogon} {$_.lastLogon = ([datetime]::FromFileTime($_.lastLogon)).ToString('g')}
            {($_.pwdLastSet) -and ($listOfBadDateValues -contains $_.pwdLastSet) -or (([datetime]::FromFileTime($_.pwdLastSet)).ToString('g') -eq $maxDateValue)} {$_.pwdLastSet = $null}
            {$_.pwdLastSet} {$_.pwdLastSet = ([datetime]::FromFileTime($_.pwdLastSet)).ToString('g')}
            default {$_}
}

That should effectively do the same thing as what you had going.

Now, for your memberOf issue, I'm betting that this change will resolve that for you:

$hashUserProperty.$userProperty = ($record.Properties[$userProperty] -join '; ').trim('; ')

That will take any collections and make them a single semicolon joined item (better for exporting to CSV), and if it's a collection with empty values it will remove extra semicolons and spaces afterwards.

CSV is not a good format for including memberOf, because it does not manage nested arrays very well. That is why I have joined the memberOf collection into a single string. If you want to preserve the format better you either need to iterate for all items in memberOf, or, probably better, use a format that does handle nested arrays such as XML or JSON.