none
A better way to do this? RRS feed

  • Question

  • Hey everyone.

    I've been using a powershell script I pieced together to dump systems from AD and determine if they are 'stale'.

    This script has worked perfectly fine for a few years, but not I'm running into the 'invalid enumeration context' error with get-adcomputer.

    My script is now taking over 30 minutes to run and thus producing the above error.

    Here is the code:

    $d = (Get-Date).AddDays(-90)
    $default_log = $env:userprofile + '\Documents\AD_Computer_Report.csv'
    
    Foreach($domain in (get-adforest).domains){
    Get-ADComputer  -Filter {(isCriticalSystemObject -eq $False)} -Properties UserAccountControl,`
    PwdLastSet,WhenChanged,samAccountName,Name,LastLogonTimeStamp,Enabled,admincount,IPv4Address,`
    operatingsystem,operatingsystemversion,serviceprincipalname,description  -server $domain |
    select @{name='Domain';expression={$domain}}, `
    Name,operatingsystem,operatingsystemversion,Enabled,IPv4Address, `
    @{Name="Stale";Expression={if((($_.pwdLastSet -lt $d.ToFileTimeUTC()) -and ($_.pwdLastSet -ne 0)`
     -and ($_.LastLogonTimeStamp -lt $d.ToFileTimeUTC()) -and ($_.LastLogonTimeStamp -ne 0)`
      -and ($_.admincount -ne 1) -and ($_.IPv4Address -eq $null)) -and `
      (!($_.serviceprincipalname -like "*MSClusterVirtualServer*"))){$True}else{$False}}}, `
    @{Name="ParentOU";Expression={$_.distinguishedname.Substring($_.samAccountName.Length + 3)}},description `
    | export-csv $default_log -append -NoTypeInformation}

    I'm trying to build a new script that uses adsisearcher instead, but I'm not able to figure out how to pull the IP address, enabled status, or parent OU.

    Here is that script:

    $default_log = $env:userprofile + '\Documents\Updated_AD_Comp_Report.csv'
    $searcher = ([adsisearcher]"(&(objectclass=computer))")
    $searcher.PageSize = 200
    #$searcher.SizeLimit = "5"
    $searcher.PropertiesToLoad.AddRange(('UserAccountControl','PwdLastSet','WhenChanged','samAccountName','Name','LastLogonTimeStamp','Enabled','admincount','IPv4Address','operatingsystem','operatingsystemversion','serviceprincipalname','description'))
    $output = 
    Foreach ($ComputerAccount in $searcher.FindAll()){
        New-Object -TypeName PSObject -Property @{
            UserAccountControl = $ComputerAccount.properties.useraccountcontrol -as [string]
            PwdLastSet = $ComputerAccount.properties.pwdlastset -as [string]
            WhenChanged = $ComputerAccount.properties.whenchanged -as [string]
            samAccountName = $ComputerAccount.properties.samaccountname -as [string]
            Name = $ComputerAccount.properties.name -as [string]
            LastLogonTimeStamp = $ComputerAccount.properties.lastlogontimestamp -as [string]
            Enabled = $ComputerAccount.properties.enabled -as [string]
            admincount = $ComputerAccount.properties.admincount -as [string]
            IPv4Address = $ComputerAccount.properties.IPv4Address -as [string]
            operatingsystem = $ComputerAccount.properties.operatingsystem -as [string]
            operatingsystemversion = $ComputerAccount.properties.operatingsystemversion -as [string]
            serviceprincipalname = $ComputerAccount.properties.serviceprincipalname -as [string]
            description = $ComputerAccount.properties.description -as [string]
        }
    }
    $output | export-csv $default_log -append -NoTypeInformation

    Ideally I would be able to get my original script working using get-adcomputer, but I'm open to any recommendations.

    Thanks for any help!


    Tuesday, November 13, 2018 1:34 PM

Answers

  • Adding I Richards bits as well as the computer special bits.

    $default_log = Join-Path $env:userprofile '\Documents\Updated_AD_Comp_Report.csv'
    $searcher = [adsisearcher]'(objectclass=computer)'
    $searcher.PageSize = 200
    $properties = @('ipv4address', 'distinguishedname', 'UserAccountControl', 'PwdLastSet', 'WhenChanged', 'samAccountName', 'Name', 'LastLogonTimeStamp', 'Enabled', 'admincount', 'IPv4Address', 'operatingsystem', 'operatingsystemversion', 'serviceprincipalname', 'description')
    $searcher.PropertiesToLoad.AddRange($properties)
    $searcher.FindAll() |
        ForEach-Object{
            $c = Get-AdComputer $_.properties['distinguishedname'][0] -Properties Ipv4Address
            [pscustomobject]@{
                Name = $_.properties['name'][0]
                Parent = ([adsi]('LDAP://' + $_.properties['distinguishedname'][0])).Parent
                UserAccountControl = $_.properties['useraccountcontrol'][0]
                PwdLastSet = $_.properties['pwdlastset'][0]
                WhenChanged = $_.properties['whenchanged'][0]
                samAccountName = $_.properties['samaccountname'][0]
                LastLogonTimeStamp = $_.properties['lastlogontimestamp'][0]
                Enabled = -not ($_.properties['useraccountcontrol'][0] -band 2)
                admincount = $_.properties['admincount'][0]
                IPv4Address = $c.IPv4Address
                operatingsystem = $_.properties['operatingsystem'][0]
                operatingsystemversion = $_.properties['operatingsystemversion'][0]
                serviceprincipalname = $_.properties['serviceprincipalname'][0]
                description = $_.properties['description'][0]
            }
        } | 
            export-csv $default_log -append -NoTypeInformation
    

    With this method property names must be all lowercase.


    \_(ツ)_/

    • Marked as answer by PowerShizzle Wednesday, November 14, 2018 12:38 AM
    Tuesday, November 13, 2018 3:19 PM

All replies

  • I'll reply to the adsisearcher part.

    There are "quality of life" attributes that Get-ADComputer / Get-ADUser add which aren't originally part of the AD object you are querying. IP Address is one of them, so you'll have to fetch the IP address some other way. Enabled is also one of them, but you can calculate that yourself from userAccountControl along with some binary operations.

    You already have a working approach for extracting parent OU from your old script, anything wrong with applying it to your new code?

    Tuesday, November 13, 2018 1:57 PM
  • First, Enabled is a PowerShell property. ADSI Searcher only retrieves actual AD attributes. You must retrieve the userAccountControl attribute, then test the integer value with the appropriate bit mask (2) to check if the bit for disabled is set. Parent is one of the property methods exposed by ADSI. You can use code similar to:

    $DN = $ComputerAccount.properties.Item("distinguishedName")
    $Computer = [ADSI]"LDAP://$DN"
    $Parent = [ADSI]$Computer.Parent

    Edit: For Enabled, code similar to:

    $UAC = $ComputerAccount.properties.Item("userAccountControl")
    If ($UAC -band 2) {$Enabled = $False}
    Else {$Enabled = $True}
    


    Richard Mueller - MVP Enterprise Mobility (Identity and Access)


    Tuesday, November 13, 2018 2:26 PM
    Moderator
  • I'll reply to the adsisearcher part.

    There are "quality of life" attributes that Get-ADComputer / Get-ADUser add which aren't originally part of the AD object you are querying. IP Address is one of them, so you'll have to fetch the IP address some other way. Enabled is also one of them, but you can calculate that yourself from userAccountControl along with some binary operations.

    You already have a working approach for extracting parent OU from your old script, anything wrong with applying it to your new code?


    IPAddress is part of all current versions of the computer object but it is called IPv4Address.

    ADSI has a property called "Parent" which is the OU DN.


    \_(ツ)_/


    • Edited by jrv Tuesday, November 13, 2018 2:37 PM
    Tuesday, November 13, 2018 2:36 PM
  • WE can start by writing the code in a way that makes it readable and easier to update.

    $d = (Get-Date).AddDays(-90)
    $default_log = Join-Path $env:userprofile '\Documents\AD_Computer_Report.csv'
    $attr = 'UserAccountControl,PwdLastSet,WhenChanged,samAccountName,Name,LastLogonTimeStamp,Enabled,admincount,IPv4Address,operatingsystem,operatingsystemversion,serviceprincipalname,description'
    $properties = $attr-split ','
    $select = @(
        @{n='Domain';e={$_.dnshostname.SubString($_.Name.Length+1)}},
        'Name','operatingsystem','operatingsystemversion','Enabled','IPv4Address',
        @{n='Stale';e={
                if(
                    (($_.pwdLastSet -lt $d.ToFileTimeUTC()) -and 
                    ($_.pwdLastSet -ne 0) -and 
                    ($_.LastLogonTimeStamp -lt $d.ToFileTimeUTC()) -and 
                    ($_.LastLogonTimeStamp -ne 0) -and 
                    ($_.admincount -ne 1) -and 
                    ($_.IPv4Address -eq $null)) -and
                    (!($_.serviceprincipalname -like '*MSClusterVirtualServer*'))
                ){$True}else{$False}}},
        @{n='ParentOU';e={$_.distinguishedname.Substring($_.samAccountName.Length + 3)}},
        'description'
    )
    
    (Get-AdForest).Domains |
        ForEach-Object{
            Get-ADComputer -Filter {isCriticalSystemObject -eq $False} -Properties $properties -server $_
        } |
        Select-Object $select | 
        export-csv $default_log -NoTypeInformation

    This uses the pipeline and can prevent stalls.  Invalid enumeration means that you may not have permission on some items on remote domains.  To find this add the properties in groups until you find the one that is failing.  Using the pipeline will help you determine which system or domain is at issue.


    \_(ツ)_/



    • Edited by jrv Tuesday, November 13, 2018 2:43 PM
    Tuesday, November 13, 2018 2:40 PM
  • The IP Addresses (v4 and v6) are not saved in Active Directory. The Get-ADComputer cmdlet retrieves the addresses using [system.net.dns]::GetHostEntry($ComputerName).AddressList, where $ComputerName is the name of the computer. This returns an array. See this blog post that shows how to retrieve the values:

    https://www.myotherpcisacloud.com/post/IPv4Address-Attribute-In-Get-ADComputer


    Richard Mueller - MVP Enterprise Mobility (Identity and Access)

    Tuesday, November 13, 2018 2:50 PM
    Moderator
  • Here is the correct way to decode ADSI properties.

    $default_log = Join-Path $env:userprofile '\Documents\Updated_AD_Comp_Report.csv'
    $searcher = [adsisearcher]'(objectclass=computer)'
    $searcher.PageSize = 200
    $properties = @('UserAccountControl','PwdLastSet','WhenChanged','samAccountName','Name','LastLogonTimeStamp','Enabled','admincount','IPv4Address','operatingsystem','operatingsystemversion','serviceprincipalname','description')
    $searcher.PropertiesToLoad.AddRange($properties)
    $searcher.FindAll() |
        ForEach-Object{
            [pscustomobject]@{
                UserAccountControl = $_.properties.useraccountcontrol[0]
                PwdLastSet = $_.properties.pwdlastset[0]
                WhenChanged = $_.properties.whenchanged[0]
                samAccountName = $_.properties.samaccountname[0]
                Name = $_.properties.name[0]
                LastLogonTimeStamp = $_.properties.lastlogontimestamp[0]
                Enabled = $_.properties.enabled[0]
                admincount = $_.properties.admincount[0]
                IPv4Address = $_.properties.IPv4Address[0]
                operatingsystem = $_.properties.operatingsystem[0]
                operatingsystemversion = $_.properties.operatingsystemversion[0]
                serviceprincipalname = $_.properties.serviceprincipalname[0]
                description = $_.properties.description[0]
            }
        } | 
        export-csv $default_log -append -NoTypeInformation
    All ADSI properties come wrapped which requires a subscript. There are other methods. Properties that have not been set will throw an exception and code will have to be added to address that.


    \_(ツ)_/



    • Edited by jrv Tuesday, November 13, 2018 2:59 PM
    Tuesday, November 13, 2018 2:57 PM
  • This method with ADSI will avoid exceptions on null values

    $default_log = Join-Path $env:userprofile '\Documents\Updated_AD_Comp_Report.csv'
    $searcher = [adsisearcher]'(objectclass=computer)'
    $searcher.PageSize = 200
    $properties = @('UserAccountControl','PwdLastSet','WhenChanged','samAccountName','Name','LastLogonTimeStamp','Enabled','admincount','IPv4Address','operatingsystem','operatingsystemversion','serviceprincipalname','description')
    $searcher.PropertiesToLoad.AddRange($properties)
    $searcher.FindAll() |
        ForEach-Object{
            [pscustomobject]@{
                UserAccountControl = $_.properties['useraccountcontrol'][0]
                PwdLastSet = $_.properties['pwdlastset'][0]
                WhenChanged = $_.properties['whenchanged'][0]
                samAccountName = $_.properties['samaccountname'][0]
                Name = $_.properties['name'][0]
                LastLogonTimeStamp = $_.properties['lastlogontimestamp'][0]
                Enabled = $_.properties['enabled'][0]
                admincount = $_.properties['admincount'][0]
                IPv4Address = $_.properties['IPv4Address'][0]
                operatingsystem = $_.properties['operatingsystem'][0]
                operatingsystemversion = $_.properties['operatingsystemversion'][0]
                serviceprincipalname = $_.properties['serviceprincipalname'][0]
                description = $_.properties['description'][0]
            }
        } | 
        export-csv $default_log -append -NoTypeInformation
    


    \_(ツ)_/

    Tuesday, November 13, 2018 3:06 PM
  • Adding I Richards bits as well as the computer special bits.

    $default_log = Join-Path $env:userprofile '\Documents\Updated_AD_Comp_Report.csv'
    $searcher = [adsisearcher]'(objectclass=computer)'
    $searcher.PageSize = 200
    $properties = @('ipv4address', 'distinguishedname', 'UserAccountControl', 'PwdLastSet', 'WhenChanged', 'samAccountName', 'Name', 'LastLogonTimeStamp', 'Enabled', 'admincount', 'IPv4Address', 'operatingsystem', 'operatingsystemversion', 'serviceprincipalname', 'description')
    $searcher.PropertiesToLoad.AddRange($properties)
    $searcher.FindAll() |
        ForEach-Object{
            $c = Get-AdComputer $_.properties['distinguishedname'][0] -Properties Ipv4Address
            [pscustomobject]@{
                Name = $_.properties['name'][0]
                Parent = ([adsi]('LDAP://' + $_.properties['distinguishedname'][0])).Parent
                UserAccountControl = $_.properties['useraccountcontrol'][0]
                PwdLastSet = $_.properties['pwdlastset'][0]
                WhenChanged = $_.properties['whenchanged'][0]
                samAccountName = $_.properties['samaccountname'][0]
                LastLogonTimeStamp = $_.properties['lastlogontimestamp'][0]
                Enabled = -not ($_.properties['useraccountcontrol'][0] -band 2)
                admincount = $_.properties['admincount'][0]
                IPv4Address = $c.IPv4Address
                operatingsystem = $_.properties['operatingsystem'][0]
                operatingsystemversion = $_.properties['operatingsystemversion'][0]
                serviceprincipalname = $_.properties['serviceprincipalname'][0]
                description = $_.properties['description'][0]
            }
        } | 
            export-csv $default_log -append -NoTypeInformation
    

    With this method property names must be all lowercase.


    \_(ツ)_/

    • Marked as answer by PowerShizzle Wednesday, November 14, 2018 12:38 AM
    Tuesday, November 13, 2018 3:19 PM
  • The IP Addresses (v4 and v6) are not saved in Active Directory. The Get-ADComputer cmdlet retrieves the addresses using [system.net.dns]::GetHostEntry($ComputerName).AddressList, where $ComputerName is the name of the computer. This returns an array. See this blog post that shows how to retrieve the values:

    https://www.myotherpcisacloud.com/post/IPv4Address-Attribute-In-Get-ADComputer


    Richard Mueller - MVP Enterprise Mobility (Identity and Access)

    Yes.  That is true when using ADSI but it is always part of the current ADWS implementation and CmdLets. 

    Thanks for the blog link.  I couldn't find it.


    \_(ツ)_/

    Tuesday, November 13, 2018 3:24 PM
  • Thanks for the feedback.

    I went ahead and ran this updated code and it provided me the following error aft 30 minutes:

    Get-ADComputer : The server has returned the following error: invalid enumeration context.
    At line:24 char:9
    +         Get-ADComputer -Filter {isCriticalSystemObject -eq $False} -P ...
    +         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : NotSpecified: (:) [Get-ADComputer], ADException
        + FullyQualifiedErrorId : ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADComputer

    There are just over 104,000 computer objects in AD.

    It seems like when I use the get-adcomputer, if the script takes longer than 30 minutes, it fails.  If it will run within 30 minutes, it works fine.

    Of the 104K, only about 52K records are written to the csv file in those 30 minutes.

    Thanks everyone for your help thus far.

    Tuesday, November 13, 2018 3:31 PM
  • I'm giving this a shot now and will report back the results.
    Tuesday, November 13, 2018 3:33 PM
  • Thanks for the feedback.

    I went ahead and ran this updated code and it provided me the following error aft 30 minutes:

    Get-ADComputer : The server has returned the following error: invalid enumeration context.
    At line:24 char:9
    +         Get-ADComputer -Filter {isCriticalSystemObject -eq $False} -P ...
    +         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : NotSpecified: (:) [Get-ADComputer], ADException
        + FullyQualifiedErrorId : ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADComputer

    There are just over 104,000 computer objects in AD.

    It seems like when I use the get-adcomputer, if the script takes longer than 30 minutes, it fails.  If it will run within 30 minutes, it works fine.

    Of the 104K, only about 52K records are written to the csv file in those 30 minutes.

    Thanks everyone for your help thus far.

    Add Try/Catch and skip the bad computers.  You will have to evaluate why they are having issues. That is beyond scripting and is a break/fix issue.


    \_(ツ)_/

    Tuesday, November 13, 2018 3:40 PM
  • Adding I Richards bits as well as the computer special bits.

    $default_log = Join-Path $env:userprofile '\Documents\Updated_AD_Comp_Report.csv'
    $searcher = [adsisearcher]'(objectclass=computer)'
    $searcher.PageSize = 200
    $properties = @('ipv4address', 'distinguishedname', 'UserAccountControl', 'PwdLastSet', 'WhenChanged', 'samAccountName', 'Name', 'LastLogonTimeStamp', 'Enabled', 'admincount', 'IPv4Address', 'operatingsystem', 'operatingsystemversion', 'serviceprincipalname', 'description')
    $searcher.PropertiesToLoad.AddRange($properties)
    $searcher.FindAll() |
        ForEach-Object{
            $c = Get-AdComputer $_.properties['distinguishedname'][0] -Properties Ipv4Address
            [pscustomobject]@{
                Name = $_.properties['name'][0]
                Parent = ([adsi]('LDAP://' + $_.properties['distinguishedname'][0])).Parent
                UserAccountControl = $_.properties['useraccountcontrol'][0]
                PwdLastSet = $_.properties['pwdlastset'][0]
                WhenChanged = $_.properties['whenchanged'][0]
                samAccountName = $_.properties['samaccountname'][0]
                LastLogonTimeStamp = $_.properties['lastlogontimestamp'][0]
                Enabled = -not ($_.properties['useraccountcontrol'][0] -band 2)
                admincount = $_.properties['admincount'][0]
                IPv4Address = $c.IPv4Address
                operatingsystem = $_.properties['operatingsystem'][0]
                operatingsystemversion = $_.properties['operatingsystemversion'][0]
                serviceprincipalname = $_.properties['serviceprincipalname'][0]
                description = $_.properties['description'][0]
            }
        } | 
            export-csv $default_log -append -NoTypeInformation

    With this method property names must be all lowercase.


    \_(ツ)_/

    This worked!

    I failed to quote the previous comment above about it not working.

    I tried this before I left work and it took just over 5 hours to run, but it worked perfectly.

    This is exactly what I needed. Thanks for the help!

    Wednesday, November 14, 2018 12:36 AM