Inventorying Computers with AD PowerShell

Inventorying Computers with AD PowerShell

  • Comments 8
  • Likes

Hi, Ned here again. Have you ever had to figure out what operating systems are running in your domain environment so that you can plan for upgrades, service pack updates, or support lifecycle transitions? Did you know that you don’t have to connect to any of the computers to find out? It’s easier than you might think, and all possible once you start using AD PowerShell in Windows Server 2008 R2 or Windows 7 with RSAT.


The cmdlet of choice for inventorying computers through AD is Get-ADComputer. This command automatically searches for computer objects throughout a domain, returning all sorts of info.

As I have written about previously my first step is to fire up PowerShell and import the ActiveDirectory module:


Then if I want to see all the details about using this cmdlet, I run:

Get-Help Get-ADComputer -Full

Getting OS information


Now I want to pull some data from my domain. I start by running the following:

Important note: in all my samples below, the lines are wrapped for readability.

Another important note (thanks dloder): I am going for simplicity and introduction here, so the -Filter and -Property switches are not designed for perfect efficiency. As you get comfortable with AD PowerShell, I highly recommend that you start tuning for less data to be returned - the "filter left, format right" model described here by Don Jones.

Get-ADComputer -Filter * -Property * | Format-Table Name,OperatingSystem,OperatingSystemServicePack,OperatingSystemVersion -Wrap –Auto


This command is filtering all computers for all their properties. It then feeds the data (using that pipe symbol) into a formatted table. The only attributes that the table contains are the computer name, operating system description, service pack, and OS version. It also automatically sizes and wraps the data. When run, I see:


It looks like I have some work to do here – one Windows Server 2003 computer needs Service Pack 2 installed ASAP. And I still have a Windows 2000 server that is going to move quickly and replace that server.

Server Filtering

Now I start breaking down the results with filters. I run:

Get-ADComputer -Filter {OperatingSystem -Like "Windows Server*"} -Property * | Format-Table Name,OperatingSystem,OperatingSystemServicePack -Wrap -Auto

I have changed my filter to find all the computers that are running “Windows Server something”, using the –like filter. And I stopped displaying the OS version data because it was not providing me anything unique (yet!).


Cool, now only servers are listed! But wait… where’d my Windows 2000 server go? Ahhhh… sneaky. We didn’t start calling OS’s “Windows Server” until 2003. Before that it was “Windows 2000 Server”. I need to massage my filter a bit:

Get-ADComputer -Filter {OperatingSystem -Like "Windows *Server*"} -Property * | Format-Table Name,OperatingSystem,OperatingSystemServicePack -Wrap -Auto

See the difference? I just added an extra asterisk to surround “Server”.


As you can see, my environment has a variety of Windows server versions running. I’m interested in the ones that are running Windows Server 2008 or Windows Server 2008 R2. And once I have that, I might just want to see the R2 servers – I have an upcoming DFSR clustering project that requires some R2 computers. I run these two sets of commands:

Get-ADComputer -Filter {OperatingSystem -Like "Windows Server*2008*"} -Property * | Format-Table Name,OperatingSystem,OperatingSystemServicePack -Wrap -Auto

Get-ADComputer -Filter {OperatingSystem -Like "Windows Server*r2*"} -Property * | Format-Table Name,OperatingSystem,OperatingSystemServicePack -Wrap -Auto



Starting to make sense? Repetition is key; hopefully you are following along with your own servers.

Workstation Filtering

Okeydokey, I think I’ve got all I need to know about servers – now what about all those workstations? I will simply switch from -Like to -Notlike with my previous server query:

Get-ADComputer -Filter {OperatingSystem -NotLike "*server*"} -Property * | Format-Table Name,OperatingSystem,OperatingSystemServicePack -Wrap -Auto

And blammo:


Family filtering

By now these filters should be making more sense and PowerShell is looking less scary. Let’s say I want to filter by the “family” of operating system. This can be useful when trying to identify computers that started having a special capability in one OS release and all subsequent releases, and where I don’t care about it being server or workstation. An example of that would be BitLocker – it only works on Windows Vista, Windows Server 2008, and later. I run:

Get-ADComputer -Filter {OperatingSystemVersion -ge "6"} -Property * | Format-Table Name,OperatingSystem,OperatingSystemVersion -Wrap -Auto

See the change? I am now filtering on operating system version, to be equal to or greater than 6. This means that any computers that have a kernel version of 6 (Vista and 2008) or higher will be returned:


If I just wanted my Windows Server 2008 R2 and Windows 7 family of computers, I can change my filter slightly:

Get-ADComputer -Filter {OperatingSystemVersion -ge "6.1"} -Property * | Format-Table Name,OperatingSystem,OperatingSystemVersion -Wrap -Auto


Getting it all into a file

So what we’ve done ‘til now was just use PowerShell to send goo out to the screen and stare. In all but the smallest domains, though, this will soon get unreadable. I need a way to send all this out to a text file for easier sorting, filtering, and analysis.

This is where Export-CSV comes in. With the chaining of an additional pipeline I can find all the computers, select the attributes I find valuable for them, then send them into a comma-separated text file that is even able to read the weirdo UTF-8 trademark characters that lawyers sometimes make us put in AD.

Hey, what do you call a million lawyers at the bottom of the ocean? A good start! Why don’t sharks eat lawyers? Professional courtesy! What do have when a lawyer is buried up to his neck in sand? Not enough sand! Haw haw… anyway:

Get-ADComputer -Filter * -Property * | Select-Object Name,OperatingSystem,OperatingSystemServicePack,OperatingSystemVersion | Export-CSV AllWindows.csv -NoTypeInformation -Encoding UTF8


Then I just crack open the AllWindows.CSV file in Excel and:


What about the whole forest?

You may be tempted to take some of the commands above and tack on the necessary arguments to search the entire forest. This means adding:

-searchbase “” –server <domain FQDN>:3268

That way you wouldn’t have to connect to a DC in every domain for the info – instead you’d just ask a single GC. Unfortunately, this won’t work; none of the operating system attributes are replicated by global catalog servers. Oh well, that’s not PowerShell’s fault. All the data must be pulled from domains individually, but that can be automated – I leave that to you as a learning exercise.


The point I made above about support lifecycle is no joke: 2010 is a very important year for a lot of Windows products’ support:

Hopefully these simple PowerShell commands make hunting down computers a bit easier for you.

Until next time.

- Ned “bird dog” Pyle

  • The statement "And I stopped asking for the OS version data" is not accurate.  The "-Property *" clause causes all properties to be returned.  All that happened is that you stopped displaying most of the data.  You still asked AD for the data.  AD still shipped it over the wire back to you.  Powershell just threw it away locally.

    The Filter Left, Format Right article from the September 2009 issue of Technet Magazine does a decent job of trying to get people to trim the pipeline as early as possible.  The same concept holds for the list of property values.  You should be using (and informating others to use) both the Property and Filter parameters to trim the output up front and only ask for the data needed.

  • Your first point is correct and I changed the wording.

    To your other main point though: as you can see, this post is designed around introduction to PowerShell for admins. The command-line is already long and cryptic enough to a novice - who is likely uncomfortable enough already - without perfectly forming the front of the pipeline. The amount of efficiency gained here is neglible compared to:

    1. The fact that this is showing a rare use inventory and not automation.

    2. Folks are getting their feet wet. They will learn filter left, format right.

    Thanks for the good feedback,

    - Ned

  • And I updated the post with your point to insure it doesn't get lost in the unread comments. Thanks again.

    - Ned

  • Really good blog Ned.  I've still been going to my favorite tool(adfind) for stuff like this but powershell is definitely another great tool.  

    For now the Quest cmdlets for where I am...Federal agency that is not yet at 2008 R2 (or even 2008).

    By the way is your "bird dog" nickname from the movie Bottle Rocket



  • I love the AD cmdlets, but one strange behavior I noticed is that error suppression via –erroraction doesn’t do the trick.

    e.g. when calling the get-adcomputer cmdlets in association with the name of a none-existing computername, I’d like to avoid an error being thrown, but no way ...

    Is this on purpose?

    C:\> get-adcomputer dummy -ea silentlycontinue

    Get-ADComputer : Cannot find an object with identity: 'dummy' under: 'DC=LABO,DC=LOCAL'.

    At line:1 char:15

    + get-adcomputer <<<<  dummy -ea silentlycontinue

       + CategoryInfo          : ObjectNotFound: (dummy:ADComputer) [Get-ADComputer], ADIdentityNotFoundException

       + FullyQualifiedErrorId : Cannot find an object with identity: 'dummy' under: 'DC=LABO,DC=LOCAL'.,Microsoft.ActiveDirectory.Management.Commands.GetADComputer

  • Hmmm - I am able to reproduce that. Very irritating, I'll see if I can get an answer on that.

    As a rather lame workaround, I was able to set the global erroraction and it worked - you could do this in blocks around your 'might not find a computer and that's ok' code.

    $errorActionPreference = "SilentlyContinue"

    get-adcomputer fdfdfdsfsd

    $errorActionPreference = "stop"

    More info:

    get-help about_preference_variables

  • And yes, I did find an internal discussion on this, it's a known issue.

    I also found this, which is a much better solution than my first workaround:


    You could use this helper function to verify that an object exists before attempting to run further cmdlets against it.

    - Ned