Query Active Directory and Ping Each Computer in the Domain by Using PowerShell

Query Active Directory and Ping Each Computer in the Domain by Using PowerShell

  • Comments 2
  • Likes

Summary: Query Active Directory and ping each computer in the domain by using Windows PowerShell. The Scripting Guys show you how.

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I am THE IT person at my company. I am also a chemical engineer who has daily production responsibilities in our process plant. You see, I am supposed to be a part-time IT person, the problem is my collateral duty is rapidly becoming my primary duty. I feel as if I should become a MCSE, but it should not be this hard. I have been learning Windows PowerShell because I think it will make my job easier. The first thing I need to do is to find out what computers are on the network. We are running Windows Server 2008, and cannot upgrade to Windows Server 2008 R2 because our hardware will not support a 64 bit OS. Therefore, the Windows PowerShell Active Directory cmdlets you have been talking about are not available to me. I think if I could get a list of all the computers in our domain and send them a ping to find out if they actually exist it would be helpful. Can this be done?

-- CM

 

Hey, Scripting Guy! Answer

Hello CM,

Microsoft Scripting Guy Ed Wilson here. School has officially started. The yellow school buses are trolling the neighborhood looking for passengers; newly minted students are clinging anxiously to their parent’s hands as they struggle to choose the correct bus for the prospective passenger in hand. The teacher across the street headed out early this morning with a strong sense of purpose and mission tattooed to her face. I am merely an observer to this annual ritual; watching the transactions that take place in view of my upstairs window. I have the Alan Parsons Project cranked up on my Zune HD, and as the parents pass off the passengers to the yellow conveyances, they seem to dance to the beat of unheard tunes – sort of like the film of the astronauts who first walked on the surface of the moon.

CM, in IT, we are often called upon to do things we have never done before. It does not mean that we have to go boldly where no IT pro has ever gone before, but for us the experience may be new. Most of the time, when we get to our destination, we realize we were not the first to ever accomplish the feat. The trick is to find that information prior to the task, and seek advice beforehand. One great place to find assistance is the Official Scripting Guys Forum. A great place to find scripts, of all kinds, is the Script Center Script Repository.

This week we are looking at using the [adsisearcher] type accelerator to search Active Directory Domain Services (AD DS). I decided to write a script that returns a list of all computers in the domain, and then pings each one to see if it responds or not. The complete Get-ADComputersTestConnection.ps1 script is seen here.

Get-ADComputersTestConnection.ps1

Function Get-ADComputersTestConnection
{
 Param(
  [switch]$showErrors
 )
 ([adsisearcher]"objectcategory=computer").findall() |
 ForEach-Object {
  try
    {
     Test-Connection -ComputerName ([adsi]$_.path).cn -BufferSize 16 `
         -Count 1 -TimeToLive 1 -EA stop }
  Catch [system.exception]
    { 
      if($showErrors)
        { $error[0].tostring() }
    }
 } #end foreach-object
} #End function Get-ADComputersTestConnection

# *** EntryPoint to Script ***

Get-ADComputersTestConnection -showErrors

CM, I decided to ensconce the code in a function named Get-ADComputersTestConnection. This function, does not really meet my requirements for a function, in that it is rather specialized, not configurable, and limited in its reuse capability. For example, because the function queries for all computers in the domain, it will have limited appeal for use in large domains. However, I did not want to clutter the function with lots of confusing options, and parameters. In addition, the use of the Test-Connection cmdlet does not expose the myriad options available for utilizing that cmdlet. Other than that, the Get-ADComputersTestConnection function is relatively cool.

CM, the Get-ADComputersTestConnection.ps1 script simply calls the Get-ADComputersTestConnection function and passes the showErrors switch. This switch causes the Get-ADComputersTestConnection function to display status information about computers that are online, as well as the error that is generated when the computer is not online. If you feel that most of the time when you use the script, you only want information on computers that are online you should remove the switch. If, on the other hand, you want to know the status of all computers, you will use the script exactly as written. This is shown in the following image.

Image of script having run

If you see yourself needing both capabilities, on a regular basis, the script will need a command line switch. There are only two changes for the Get-ADComputersTestConnectionSwitch.ps1 script. The first is the command line parameter to allow the showing of the errors. This is shown here:

Param([switch]$showErrors)

The next change is the logic at the entry point of the script to detect if the command line switch has been used when launching the script. A simple if … else statement is used. If the $showErrors variable is present on the Windows PowerShell variable drive, it means the script was launched with the –showErrors command line parameter. The if … else statement checks for the existence of this variable. If the $showErrors variable is found, the Get-ADComputersTestConnection function is called while passing the –showErrors parameter. Once the function completes, the script exists. If the variable is not found, the function is called with no parameters. This code is seen here.

if($showErrors) { Get-ADComputersTestConnection -showErrors ; exit }
Else { Get-ADComputersTestConnection ; exit }

The modification to use the script with a command line switch is shown here.

Get-ADComputersTestConnectionSwitch.ps1

Param([switch]$showErrors)

Function Get-ADComputersTestConnection
{
 Param(
  [switch]$showErrors
 )
 ([adsisearcher]"objectcategory=computer").findall() |
 ForEach-Object {
  try
    {
     Test-Connection -ComputerName ([adsi]$_.path).cn -BufferSize 16 `
         -Count 1 -TimeToLive 1 -EA stop }
  Catch [system.exception]
    { 
      if($showErrors)
        { $error[0].tostring() }
    }
 } #end foreach-object
} #End function Get-ADComputersTestConnection

# *** Entry Point to Script ***

if($showErrors) { Get-ADComputersTestConnection -showErrors ; exit }
Else { Get-ADComputersTestConnection ; exit }

When the Get-ADComputersTestConnectionSwitch.ps1 script is run with no parameters, the status of each computer in AD DS is displayed. Errors in testing the connection to the remote computers are not displayed. When the script is run with the –showErrors parameter, both the connection status and errors are displayed. The command line syntax and sample output for each is shown in the following image.

Image of command-line syntax and sample output

Inside the Get-ADComputersTestConnection function, after the parameter statement, the first thing that is done is the [adsisearcher] type accelerator is used to search AD DS. The “objectcategory=computer” filter is used, and the findall method is called to return all instances of computer objects in AD DS. This section of code was discussed in detail in yesterday’s Hey Scripting Guy! Blog post.

Inside a pipeline, when you need to work on one item at a time, use the Foreach-Object cmdlet. You will often see its’ alias on blogs “%” or occasionally you will see it referred to by its’ other alias foreach. While the first alias is simply obscure, the second alias is downright confusing because foreach is also a keyword for the foreach statement. If the two commands worked the same, it would be merely inconvenient, but for people attempting to learn Windows PowerShell, they expect foreach to always be the foreach statement, and that is not the case.

The ForeachComputer.ps1 script is an example about which I am talking.

ForeachComputer.ps1

$computers = ([adsisearcher]"objectcategory=computer").findall()
foreach ($computer in $computers)
{
 $computer.path
}

On the first line of the script, the results of the directory searcher query are stored in the $computers variable. Because this is a collection of objects, it is necessary to use the foreach statement to work through the collection to display the path of each computer. The foreach statement is comprised of three parts. The first is the variable to use as the placeholder (enumerator) in the collection. The second part is the collection itself. These first parts go inside a set of parentheses. The script block (code block) is placed inside a pair of curly brackets (braces). The code in the script block is executed on each item in the collection. That item is referenced by the placeholder variable.

The foreach-object cmdlet is always used in a pipeline. The foreach-object statement does not use the set of parentheses. It uses the $_ automatic variable to work with each object as it comes down the pipeline. The results from the two scripts are the same. One advantage of using the pipeline, and the foreach-object cmdlet is the code is more compact; there is no need to create a variable to store the results into, nor is there a need to create a variable for the placeholder as the script walks through the collection of computers. The Foreach-ObjectComputer.ps1 script is seen here.

Foreach-objectComputer.ps1

([adsisearcher]"objectcategory=computer").findall() |
ForEach-Object {
 $_.path
}

Which approach is faster? Using my Test-TwoScripts.ps1 script, on my network the Foreach-objectComputer.ps1 script is 26% faster. You will need to test to see if you achieve similar results on your network.

Inside the foreach-object block of the Get-ADComputersTestConnection.ps1 script, the first thing that is done, is the try statement is used to attempt to use the Test-Connection cmdlet to send a PING to the remote computer. The problem, is the result of the [adsisearcher] is a search result, not a directory entry object. Therefore, access to the normal ADSI types of properties are not available. One thing that can be access is the path. This is seen here.

PS C:\> $computers = ([adsisearcher]"objectcategory=computer").findall()
PS C:\> ($computers[0]).gettype()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    SearchResult                             System.Object


PS C:\> $computers[0].path
LDAP://CN=HYPERV,OU=Domain Controllers,DC=NWTraders,DC=Com
PS C:\>

This path is exactly what is required to obtain a directoryentry object from the [adsi] type accelerator. This is seen here.

PS C:\> [adsi]$computers[0].path


distinguishedName : {CN=HYPERV,OU=Domain Controllers,DC=NWTraders,DC=Com}
Path                      : LDAP://CN=HYPERV,OU=Domain Controllers,DC=NWTraders,DC=Com



PS C:\> ([adsi]$computers[0].path).gettype()

IsPublic IsSerial Name                                     BaseType
--------    --------    ----                                          --------
True      False    DirectoryEntry                         System.ComponentModel....


PS C:\>

The code inside the parentheses seen above returns a DirectoryEntry object. It can be used to access the normal ADSI attributes. The CN attribute is retrieved from the first computer in the computers collection. This is seen here.

PS C:\> ([adsi]$computers[0].path).cn

HYPERV

PS C:\>  

The cn attribute is the name of the computer that can be pinged. If the computer is not found, the erroraction preference is set to stop (EA is an alias for the erroraction parameter). By using stop here, it will force the script to enter the catch block. The try block is seen here.

try
    {
     Test-Connection -ComputerName ([adsi]$_.path).cn -BufferSize 16 `
         -Count 1 -TimeToLive 1 -EA stop }

The catch block is used to catch any system exception. This is needed because the Test-Connection cmdlet returns a rather nasty looking error when a computer is not found. This is seen here.

PS C:\> Test-Connection bogus -BufferSize 16 -Count 1

Test-Connection : Testing connection to computer 'bogus' failed: The requested name

is valid, but no data of the requested type was found

At line:1 char:16

+ Test-Connection <<<<  bogus -BufferSize 16 -Count 1

    + CategoryInfo          : ResourceUnavailable: (bogus:String) [Test-Connection]

   , PingException

    + FullyQualifiedErrorId : TestConnectionException,Microsoft.PowerShell.Commands

   .TestConnectionCommand
 

This error is stored inside the $error variable. The $error variable holds an array of errors, with the most recent error always occupying position [0]. To retrieve the most recent error, you access $error[0] as seen here.

PS C:\> $error[0]

Test-Connection : Testing connection to computer 'bogus' failed: The requested name

is valid, but no data of the requested type was found

At line:1 char:16

+ Test-Connection <<<<  bogus -BufferSize 16 -Count 1

    + CategoryInfo          : ResourceUnavailable: (bogus:String) [Test-Connection]

   , PingException

    + FullyQualifiedErrorId : TestConnectionException,Microsoft.PowerShell.Commands

   .TestConnectionCommand
 

In this example, the error shown in $error[0] looks pretty much like the one that was displayed to the Windows PowerShell console. The $error variable contains really rich objects, however, and there is a lot of information that can be obtained by working through the error object model. However, to obtain a more friendly looking error, all I need to do is call the tostring() method as seen here.

PS C:\> $error[0].tostring()

Testing connection to computer 'bogus' failed: The requested name is valid, but no d

ata of the requested type was found
 

I decided to display the error by using the tostring() method because the information returned is actually helpful in determining why the computer is not responsive. For example, in using test-connection to test a connection to a computer named bogus, it says the name is valid, but no data of the requested type was found. Other errors that are returned are likewise informative. Here is the catch portion of the script.

Catch [system.exception]
    { 
      if($showErrors)
        { $error[0].tostring() }
    }

CM, that is all there is to using Windows PowerShell to search AD DS. Searching Active Directory Domain Services week will continue tomorrow when we talk about searching AD DS for computers,. We would love for you to follow us on Twitter and Facebook. If you have any questions, send email to us at scripter@microsoft.com or post them on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys


Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • How could I modify this script to search a specific OU?

  • Isn't foreach just an alias for the ForEach-Object cmdlet?