2013/10/31

PowerShell - Get-DomainComputer (ADSI)


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


PowerShell 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 AND. The two 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
PS C:\> Get-Computer -ComputerName "LAB1DC01"
DNShostName                Description                Name                     
-----------                -----------                ----                     
LAB1DC01.FX.LAB            Domain Controller of FX... LAB1DC01

Against multiple computer objects
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 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


Or 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 ...

Quick look at the PowerShell 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
      • OR
    • 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*"
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:@lazywinadm
        
        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 (Advanced Solution)

Technet Repository


Thanks for Reading! If you have any questions, leave a comment or send me an email at [email protected] I invite you to follow me on Twitter @lazywinadm / Google+ / LinkedIn You can also follow the LazyWinAdmin Blog on Facebook Page and Google+ Page

No comments:

Post a Comment