8 minute read

Note: FYI, more ADSI functions are available in the ADSIPS PowerShell module here: https://github.com/lazywinadmin/adsips

The following function use ADSI to query Computer objects from the Active Directory. Optionally an alternate credentials and/or a different domain can be specified.

Once in a while, everyone “enjoy” doing Auditing at work…,ok maybe not everyone :-)…. so last week, an colleague of mine needed to get the name of the Primary user of each workstations that connect to one of their critical application.

Lucky for me, first he had the list of workstations and second we have the name of the primary user information in the Active Directory located in the description property ! :-) (This is added when the computer is built and joined to the domain the first time).

So he asked me if I could help and get this information somehow. My answer was obviously … PowerShell! This could be done very easily using the ActiveDirectory Module but unfortunately RSAT (Remote Server Administrator Tools) feature was not installed on his computer. Why not use ADSI then ? :-)

If you follow my blog, in my previous posts I wrote about a small PowerShell function Get-DomainUser that use ADSI to get some information out of a Active Directory User Object and about Using alternate credential for ADSI query.

Today we will use the same techniques to get information from Active Directory Computer Object(s).

SOLUTION #1: The Lazy way

Code

This is the small function I originally sent him, nothing fancy but it does the work.

Function Get-Computer {
  [CmdletBinding()]
  PARAM(
    [Parameter(
      ValueFromPipelineByPropertyName=$true,
      ValueFromPipeline=$true,
      Mandatory=$true)]
    [String[]]$ComputerName)
  PROCESS{
    FOREACH ($item in $ComputerName){
      $Search = [adsisearcher]"(&(objectCategory=Computer)(name=$item))"
      FOREACH ($Computer in $($Search.FindAll())){
        New-Object -TypeName PSObject -Property @{
          "Name" = $($Computer.properties.name)
          "DNShostName"    = $($Computer.properties.dnshostname)
          "Description" = $($Computer.properties.description)}
      }#foreach ($Computer in $($Search.FindAll
    }#FOREACH ($item in $ComputerName){
  }#PROCESS{
}#function Get-Computer

[ADSISearcher] Basically, I’m creating a [ADSISearcher] object with a filter which contains the two following conditions:

  • (objectCategory=Computer) which only show the Computer object
  • ComputerName parameter specified by the user

Notice the & logical operator which can be translated to an AND operator, means the following conditions must be met. The execution of the search will not be perform until you actually use the FindOne() or FindAll() methods

   TypeName: System.DirectoryServices.DirectorySearcher

Name    MemberType Definition
----    ---------- ----------
FindAll Method     System.DirectoryServices.SearchResultCollection FindAll()
FindOne Method     System.DirectoryServices.SearchResult FindOne()
PS C:\> $Search = [adsisearcher]"(&(objectCategory=Computer)(name=DHCP1))"
PS C:\> $Search.findall()

Path                                              Properties
----                                              ----------
LDAP://CN=DHCP1,CN=Computers,DC=FX,DC=LAB         {logoncount, codepage, objectcategory, descrip...

The full list of properties is available by doing the following

PS C:\> ($Search.findall()).properties

Name                           Value
----                           -----
logoncount                     {17}
codepage                       {0}
objectcategory                 {CN=Computer,CN=Schema,CN=Configuration,DC=FX,DC=LAB}
description                    {DHCP Server}
operatingsystem                {Windows Server 2012 Standard}
usnchanged                     {229459}
instancetype                   {4}
name                           {DHCP1}
badpasswordtime                {0}
pwdlastset                     {130209415121221390}
serviceprincipalname           {WSMAN/DHCP1.FX.LAB, RestrictedKrbHost/DHCP1.FX.LAB, HOST/DHCP1.F...
objectclass                    {top, person, organizationalPerson, user...}
badpwdcount                    {0}
samaccounttype                 {805306369}
lastlogontimestamp             {130209414908434960}
usncreated                     {74657}
objectguid                     {83 104 21 241 9 61 235 78 147 246 91 68 225 41 192 76}
localpolicyflags               {0}
whencreated                    {6/1/2013 5:09:45 PM}
adspath                        {LDAP://CN=DHCP1,CN=Computers,DC=FX,DC=LAB}
useraccountcontrol             {4096}
cn                             {DHCP1}
countrycode                    {0}
primarygroupid                 {515}
whenchanged                    {10/24/2013 6:03:03 AM}
operatingsystemversion         {6.2 (9200)}
dnshostname                    {DHCP1.FX.LAB}
dscorepropagationdata          {1/1/1601 12:00:00 AM}
lastlogon                      {130209599754010756}
distinguishedname              {CN=DHCP1,CN=Computers,DC=FX,DC=LAB}
msds-supportedencryptiontypes  {28}
iscriticalsystemobject         {False}
samaccountname                 {DHCP1$}
objectsid                      {1 5 0 0 0 0 0 5 21 0 0 0 180 190 60 92 74 161 105 103 20 195 233...
lastlogoff                     {0}
displayname                    {DHCP1$}
accountexpires                 {9223372036854775807}

Output

This function will just return the Name, DNSHostName and the Description.

# Querying a specific machine
PS C:\> Get-Computer -ComputerName "LAB1DC01"

DNShostName                Description                Name
-----------                -----------                ----
LAB1DC01.FX.LAB            Domain Controller of FX... LAB1DC01


# Querying multiple machines

PS C:\> Get-Computer -ComputerName WORKSTATION01, WORKSTATION02, WORKSTATION03, WORKSTATION04

DNShostName                       Description                      Name
-----------                       -----------                      ----
WORKSTATION01.fx.lab              Bob Smith                        WORKSTATION01
WORKSTATION02.fx.lab              Jean Dupont                      WORKSTATION02
WORKSTATION03.fx.lab              F-Xavier Cat                     WORKSTATION03
WORKSTATION04.fx.lab              Jeanne St-Croix                  WORKSTATION04


# Using a ComputerName pattern

PS C:\> Get-Computer -ComputerName WORKSTATION*

DNShostName                       Description                      Name
-----------                       -----------                      ----
WORKSTATION01.fx.lab              Bob Smith                        WORKSTATION01
WORKSTATION02.fx.lab              Jean Dupont                      WORKSTATION02
WORKSTATION03.fx.lab              F-Xavier Cat                     WORKSTATION03
WORKSTATION04.fx.lab              Jeanne St-Croix                  WORKSTATION04


# Using a list of workstations instead

PS C:\> Get-Content -Path .\computers.txt | Get-Computer

DNShostName                       Description                      Name
-----------                       -----------                      ----
WORKSTATION01.fx.lab              Bob Smith                        WORKSTATION01
WORKSTATION02.fx.lab              Jean Dupont                      WORKSTATION02
WORKSTATION03.fx.lab              F-Xavier Cat                     WORKSTATION03
WORKSTATION04.fx.lab              Jeanne St-Croix                  WORKSTATION04

SOLUTION #2: The Advanced way

I spent a bit more time on this one to polish the code :

  • Comments based help,
  • Verbose,
  • Alternate credentials
  • Error handling
  • etc …

Code

This is just a part of the function that is creating a ADSI searcher object to look for a computer matching a name or a pattern specified by the user. Another part (not showed here) is taking care of listing all the Computer Objects.

[CmdletBinding()]
PARAM(
  [Parameter(
    ValueFromPipelineByPropertyName=$true,
    ValueFromPipeline=$true)]
  [Alias("Computer")]
  [String[]]$ComputerName,

  [Alias("ResultLimit","Limit")]
  [int]$SizeLimit='100',

  [Parameter(ValueFromPipelineByPropertyName=$true)]
  [Alias("Domain")]
  [String]$DomainDN=$(([adsisearcher]"").Searchroot.path),

  [Alias("RunAs")]
  [System.Management.Automation.Credential()]
  $Credential = [System.Management.Automation.PSCredential]::Empty

  )#PARAM

  PROCESS{
  IF ($ComputerName){FOREACH ($item in $ComputerName){
  TRY{
    # Building the basic search object with some parameters
    Write-Verbose -Message "COMPUTERNAME: $item"
    $Searcher = New-Object -TypeName System.DirectoryServices.DirectorySearcher `
                  -ErrorAction 'Stop' -ErrorVariable ErrProcessNewObjectSearcher
    $Searcher.Filter = "(&(objectCategory=Computer)(name=$item))"
    $Searcher.SizeLimit = $SizeLimit
    $Searcher.SearchRoot = $DomainDN

    # Specify a different domain to query
    IF ($PSBoundParameters['DomainDN']){
      IF ($DomainDN -notlike "LDAP://*") {$DomainDN = "LDAP://$DomainDN"}#IF
      Write-Verbose -Message "Different Domain specified: $DomainDN"
      $Searcher.SearchRoot = $DomainDN}#IF ($PSBoundParameters['DomainDN'])

    # Alternate Credentials
    IF ($PSBoundParameters['Credential']) {
      Write-Verbose -Message "Different Credential specified: $($Credential.UserName)"
      $Domain = New-Object -TypeName System.DirectoryServices.DirectoryEntry `
        -ArgumentList $DomainDN,$($Credential.UserName),$($Credential.GetNetworkCredential().password) `
        -ErrorAction 'Stop' -ErrorVariable ErrProcessNewObjectCred
      $Searcher.SearchRoot = $Domain}#IF ($PSBoundParameters['Credential'])

    # Querying the Active Directory
    Write-Verbose -Message "Starting the ADSI Search..."
    FOREACH ($Computer in $($Searcher.FindAll())){
      Write-Verbose -Message "$($Computer.properties.name)"
      New-Object -TypeName PSObject -ErrorAction 'Continue' `
        -ErrorVariable ErrProcessNewObjectOutput -Property @{
        "Name" = $($Computer.properties.name)
        "DNShostName"    = $($Computer.properties.dnshostname)
        "Description" = $($Computer.properties.description)
        "OperatingSystem"=$($Computer.Properties.operatingsystem)
        "WhenCreated" = $($Computer.properties.whencreated)
        "DistinguishedName" = $($Computer.properties.distinguishedname)}#New-Object
    }#FOREACH $Computer

    Write-Verbose -Message "ADSI Search completed"
  }#TRY
  CATCH{
    Write-Warning -Message ('{0}: {1}' -f $item, $_.Exception.Message)

    IF ($ErrProcessNewObjectSearcher){
      Write-Warning -Message "PROCESS BLOCK - Error during the creation of the searcher object"}
    IF ($ErrProcessNewObjectCred){
      Write-Warning -Message "PROCESS BLOCK - Error during the creation of the alternate credential object"}
    IF ($ErrProcessNewObjectOutput){
      Write-Warning -Message "PROCESS BLOCK - Error during the creation of the output object"}
  }#CATCH
}#FOREACH $item

Note: I had to use backtick ` to be able to fit the code in my blog. Backticks are not present in the final script.

To resume we added the following items :

  • Error Handling
    • TRY{"do tasks"} CATCH{"Oups Error"}
  • Verbose
    • [cmdletbinding()]
    • Write-Verbose
  • Support for Multiple ComputerName query
    • [string[]]$ComputerName
    • FOREACH ($item in $ComputerName)
  • Support for Alternative Credential
    • [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty
    • IF ($PSBoundParameters['Credential'])
    • New-Object -TypeName System.DirectoryServices.DirectoryEntry-ArgumentList $DomainDN,$($Credential.UserName),$($Credential.GetNetworkCredential().password)
  • Support for different Domain
    • IF ($PSBoundParameters['DomainDN'])
    • $Searcher.SearchRoot = $DomainDN
    • New-Object -TypeName System.DirectoryServices.DirectoryEntry-ArgumentList $DomainDN,$($Credential.UserName),$($Credential.GetNetworkCredential().password)

Output

The function generate the following output

PS C:\> Get-DomainComputer -ComputerName "lab1*"

image-center

Name              : LAB1DC01
WhenCreated       : 3/7/2013 2:19:48 AM
Description       : Domain Controller of FX.LAB
OperatingSystem   : Windows Server 2012 Standard
DNShostName       : LAB1DC01.FX.LAB
DistinguishedName : CN=LAB1DC01,OU=Domain Controllers,DC=FX,DC=LAB

Name              : LAB1VC01
WhenCreated       : 4/7/2013 5:19:17 AM
Description       : lab1vc01.fx.lab
OperatingSystem   : SLES
DNShostName       : lab1vc01.fx.lab
DistinguishedName : CN=LAB1VC01,CN=Computers,DC=FX,DC=LAB

Name              : LAB1VH02
WhenCreated       : 4/7/2013 5:33:27 AM
Description       : LAB1VH02
OperatingSystem   : unknown
DNShostName       : lab1vh02.fx.lab
DistinguishedName : CN=LAB1VH02,CN=Computers,DC=FX,DC=LAB

Name              : LAB1VH01
WhenCreated       : 4/7/2013 5:38:54 AM
Description       : LAB1VH01
OperatingSystem   : unknown
DNShostName       : lab1vh01.fx.lab
DistinguishedName : CN=LAB1VH01,CN=Computers,DC=FX,DC=LAB

Name              : LAB1SQL01
WhenCreated       : 6/23/2013 7:40:18 PM
Description       : SQL2012
OperatingSystem   : Windows Server 2012 Standard
DNShostName       : LAB1SQL01.FX.LAB
DistinguishedName : CN=LAB1SQL01,CN=Computers,DC=FX,DC=LAB

Name              : LAB1CM01
WhenCreated       : 6/23/2013 8:05:58 PM
Description       : SCCM2012
OperatingSystem   : Windows Server 2012 Standard
DNShostName       : LAB1CM01.FX.LAB
DistinguishedName : CN=LAB1CM01,CN=Computers,DC=FX,DC=LAB

Name              : LAB1OR01
WhenCreated       : 6/23/2013 9:58:41 PM
Description       : SCORCH2012
OperatingSystem   : Windows Server 2012 Standard
DNShostName       : LAB1OR01.FX.LAB
DistinguishedName : CN=LAB1OR01,CN=Computers,DC=FX,DC=LAB

Name              : LAB1VC02
WhenCreated       : 6/23/2013 10:00:28 PM
Description       : VMware vCenter
OperatingSystem   : Windows Server 2012 Standard
DNShostName       : LAB1VC02.FX.LAB
DistinguishedName : CN=LAB1VC02,CN=Computers,DC=FX,DC=LAB

Help

Can’t built a function without help! :-)

<#
PS C:\> Get-Help Get-DomainComputer -full

NAME
  Get-DomainComputer

SYNOPSIS
  The Get-DomainComputer function allows you to get information from an Active Directory
  Computer object using ADSI.

SYNTAX
  Get-DomainComputer [[-ComputerName] <String[]>] [[-SizeLimit] <Int32>] [[-DomainDN]
  <String>] [[-Credential] <Object>] [<CommonParameters>]


DESCRIPTION
  The Get-DomainComputer function allows you to get information from an Active Directory
  Computer object using ADSI.
  You can specify: how many result you want to see, which credentials to use and/or
  which domain to query.


PARAMETERS
  -ComputerName <String[]>
      Specifies the name(s) of the Computer(s) to query

      Required?                    false
      Position?                    1
      Default value
      Accept pipeline input?       true (ByValue, ByPropertyName)
      Accept wildcard characters?  false

  -SizeLimit <Int32>
      Specifies the number of objects to output. Default is 100.

      Required?                    false
      Position?                    2
      Default value                100
      Accept pipeline input?       false
      Accept wildcard characters?  false

  -DomainDN <String>
      Specifies the path of the Domain to query.
      Examples:     "FX.LAB"
                  "DC=FX,DC=LAB"
                  "Ldap://FX.LAB"
                  "Ldap://DC=FX,DC=LAB"

      Required?                    false
      Position?                    3
      Default value                $(([adsisearcher]"").Searchroot.path)
      Accept pipeline input?       true (ByPropertyName)
      Accept wildcard characters?  false

  -Credential <Object>
      Specifies the alternate credentials to use.

      Required?                    false
      Position?                    4
      Default value                [System.Management.Automation.PSCredential]::Empty
      Accept pipeline input?       false
      Accept wildcard characters?  false

  <CommonParameters>
      This cmdlet supports the common parameters: Verbose, Debug,
      ErrorAction, ErrorVariable, WarningAction, WarningVariable,
      OutBuffer and OutVariable. For more information, see
      about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216).

INPUTS

OUTPUTS

NOTES

  NAME:    FUNCT-AD-COMPUTER-Get-DomainComputer.ps1
  AUTHOR:    Francois-Xavier CAT
  DATE:    2013/10/26
  EMAIL:    [email protected]
  WWW:    www.lazywinadmin.com
  TWITTER:@lazywinadmin

  VERSION HISTORY:
  1.0 2013.10.26
      Initial Version

    -------------------------- EXAMPLE 1 --------------------------

    C:\PS>Get-DomainComputer


    This will show all the computers in the current domain





    -------------------------- EXAMPLE 2 --------------------------

    C:\PS>Get-DomainComputer -ComputerName "Workstation001"


    This will query information for the computer Workstation001.





    -------------------------- EXAMPLE 3 --------------------------

    C:\PS>Get-DomainComputer -ComputerName "Workstation001","Workstation002"


    This will query information for the computers Workstation001 and Workstation002.





    -------------------------- EXAMPLE 4 --------------------------

    C:\PS>Get-Content -Path c:\WorkstationsList.txt | Get-DomainComputer


    This will query information for all the workstations listed inside the
    WorkstationsList.txt file.





    -------------------------- EXAMPLE 5 --------------------------

    C:\PS>Get-DomainComputer -ComputerName "Workstation0*" -SizeLimit 10 -Verbose


    This will query information for computers starting with 'Workstation0', but only show
    10 results max.
    The Verbose parameter allow you to track the progression of the script.





    -------------------------- EXAMPLE 6 --------------------------

    C:\PS>Get-DomainComputer -ComputerName "Workstation0*" -SizeLimit 10 -Verbose
    -DomainDN "DC=FX,DC=LAB" -Credential (Get-Credential -Credential FX\Administrator)


    This will query information for computers starting with 'Workstation0' from the domain
    FX.LAB with the account FX\Administrator.
    Only show 10 results max and the Verbose parameter allows you to track the progression
    of the script.
#>

Download

Script can be found here

Leave a comment