Using PowerShell to query Active Directory

I first got excited about PowerShell when I wrote about it here, and then I did another post for how to interact with Active Directory, here.

Well, over the last couple days I’ve been dabbling with a PowerShell script to solve a problem at work. The first problem? We don’t have the Active Directory module available, because on a Windows workstation, it’s only available as part of the Remote Server Administration Tools download – for which I put in a request.

So, while I’m waiting for that to be approved at work, I decided to see if I could get a script going with my home domain that does approximately what I need. Since I will likely use this as a reference in the future, I thought I’d do a blog post about.

The problem:
For various reasons, often I’ll have a list of domain user ID’s, and I’ll need to look those up in Active Directory to go get their name, department, group membership, etc. I would typically write a little .NET program for this – which is sort of unwieldy. The thing the makes it even less ideal is that the people for whom I’m generating this information (managers, often), they want to “play” with query, or use that to do other things.

A solution:
Well, I can’t really give them the .NET code (they aren’t developers, and also don’t have Visual Studio). Wouldn’t it be cool instead if I could give them a PowerShell script? I mean, the scripting language is pretty self-explanatory is easy enough for even a non-programmer to play with. Even if you are new to PowerShell, the syntax and Intellisense make it plausible that if I had a script that did most of what they want, they could run and tweak the script at their whim. Best of all, PowerShell is installed on all of our workstations. So what if we created scripts that we could give to people, to let them querying to their hearts content?

At least, that’s what I’d like to try!

Pre-requirements and environment setup:
You need PowerShell 2.0 or later. PowerShell 2.0 came out with Windows 7, v3.0 came out with Windows 8, etc., etc. If you want to though, you can grab the latest version of PowerShell – see here for instructions and links for each platform. That is optional, however in order to use Active Directory stuff, you do need that PowerShell module. After some research, the only place I found to get this is the Remote Server Administration Tools. Here is the download: RSAT for Windows 7, RSAT for Windows 8.1.

Instead of working in the PowerShell command line, I highly recommend using the PowerShell ISE (Interactive Scripting Environment) – click Start –> Run (or WindowsKey+R) and type powershell_ise.exe:

image

That brings up the editor:

image

Script requirements:
OK, a common scenario is that I’m given a file with one ID per line and I need to look up the details for each user. How do we do that? Assume we have an input file like this:

image

I include an invalid user (NotThereUser) because sometimes there is a typo in the file, or sometimes the user left the company. If I have a known-error in the input file, I can account for it in the script.

In PowerShell, to read the contents of the file and iterate over each line, you can do something like this:

# The file with one user ID per line.
$inputFilePath = "C:DataUserList.txt";

# Get a handle to the file
$userIds = Get-Content $inputFilePath

# Loop through each line of the file (one domain ID per line)
ForEach ($userId in $userIds)
{
    # Do something with the $userId
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

You can probably guess what we need to do in the ForEach: we need to go to Active Directory and get the user information. First, we’ll need to prompt the user running this script for domain credentials, and specify the domain:

# If you get an error on this next line, you must install Remote Server
# Administration Tools (RSAT)
Import-Module ActiveDirectory

$domainName = "hernando.division42.com"
$userId = "rseder"

# Prompt the user for credentials
$credentials = Get-Credential `
        -Message "Please enter domain credentials for '$domainName'" `
        -UserName "$domainName$env:USERNAME";

# Query Active Directory for user details.
$userDetail = Get-ADUser -Server $domainName `
                        -Properties 'SamAccountName' `
                        -Credential $credentials `
                        -Filter { SamAccountName -eq $userId };

# Output the pipeline to the console, as a table.
$userDetail | Format-Table -AutoSize

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

That should give you output similar to this:

image

then in the PowerShell window:

image

OK, so at this point, we know how to iterate over the lines of the input file, and how to do a lookup, so now what? In my scenario, I typically need to report on users where we couldn’t find a result. So, we could iterate over the file, and append to a hashtable – the key would be the ID we are looking up, the value would be the user detail from AD, if we found it – or null if we didn’t.

If we did that, then we’d have a key/value pair collection which would have all of our data AND we could report on which users we couldn’t get information on. Here is a quick crash course on hashtables, in code:

# Create an empty hashtable
$results = @{}

# Add some items to the collection.
$results.Add("Key1","Some value here")
$results.Add("Key2", $null)
$results.Add("Key3", "Another value here")

# Output the results into a table.
$results | Format-Table -AutoSize

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

That results in output like this:

image

You might note that it sorts by Value, by default.

First draft of a working script:
At this point, I can take the ideas outlined above and make a script that does what I want:

# If you get an error on this next line, you must install Remote Server
# Administration Tools (RSAT)
Import-Module ActiveDirectory

$inputFilePath = "C:DataUserList.txt";
$domainName = "hernando.division42.com"
$userId = "rseder"

# Prompt the user for credentials
$credentials = Get-Credential `
        -Message "Please enter domain credentials for '$domainName'" `
        -UserName "$domainName$env:USERNAME"

# Get a handle to the file
$userIds = Get-Content $inputFilePath

# A hashtable where to put the results.
$results = @{}

# Loop through each line of the file (one domain ID per line)
ForEach ($userId in $userIds)
{
    # Query Active Directory for user details.
    $userDetail = Get-ADUser -Server $domainName `
                        -Properties 'SamAccountName' `
                        -Credential $credentials `
                        -Filter { SamAccountName -eq $userId }
    $results.Add($userId, $userDetail)
}

$results | Format-Table -AutoSize

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

that results in output like this:

image

Excellent! The “Name” column is the list of names from our input file, and the “Value” is the Active Directory “object” about the user, if we found them, or null if we didn’t.

Pulling it all together:
OK, at this point, there are really two things I need:

  1. Produce a list of ID’s that were not found
  2. For the ID’s that were found, go get some arbitrary fields about them from Active Directory

To product the list of ID’s that were not found, that ended up being a little tricky! See, if you are familiar with .NET, this is like a Dictionary object, so you can’t just easily access the key and value. So, this is what I came up with, which works – note, that “$_” object means “the object from the pipeline, from the previous operation”. So, if you want to do something with the object from the last call, you can reference it that way.:

# Re-structure the results into something easier to work with:
$pipeableResult = $results `
            | Select-Object -ExpandProperty Keys `
            | Select-Object -Property @{Name="UserIdToLookup";Expression={$_}}, `
                                      @{Name="UserDetail";Expression={$results[$_]}}

# Get all the items where the value was null
$notFoundList = $pipeableResult | Where-Object {$_.UserDetail -eq $null}

# Output the list of null users
Write-Host "List of user ID's not found:"
$notFoundList | Select-Object -Property UserIdToLookup | Out-GridView

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Which pops open a grid like this, in this case, we just have one entry. However, if you had a bunch you could copy them from this grid and paste into Excel or an e-mail:

image

Let me see if I can explain that “$pipeableResult”. Using the native hashtable is a huge pain, so I just created a new collection, basically, with my own field names where I can actually treat them like real properties. With a hashtable, you can’t work directory with Key and Value. Now, that $pipeableResult is a nice collection which is easy to work with:

image

OK – so we’ve satisfied the first requirement, we have a list of users where we couldn’t find information in AD. Now, for the users we could find, let’s dump out some information:

Write-Host "Launching grid to show users who matched."
$pipeableResult | Select-Object -ExpandProperty UserDetail `
                | Select-Object -Property SamAccountName, Name, DistinguishedName,Enabled `
                | Out-GridView

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

That results in grid like this:

image

Again, this is a filterable, sortable grid. You can copy from here and paste in an e-mail or Excel.

Fit and Finish:
I started off by writing this as one, big procedural script. It would be ideal to modularize some of this, add Intellisense for functions, titles to the gridviews, etc.

To create a function, and make it so you have Intellisense, you can do something like this:

##############################################################################
#.SYNOPSIS
# Uses the specified list of userIds to look up each user in the domain.
#
#.DESCRIPTION
# For each userId, look up the user user in the specified $domain, using the 
# specified $credentials.
#
#.PARAMETER userIds
# The collection of user ID's to process.
#
#.PARAMETER domain
# The domain in which to look up the users. (e.g. domain.example.com)
#
#.PARAMETER credentials
# The credentials to use when connecting to the domain. Get-Credential can be
# used to get these.
#
#.EXAMPLE
# $domain = "domain.example.com";
# $credentials = Get-Credential;
# $userIds = Get-Content "C:file.txt";
#
# $results = Get-ADUser-For-UserIds($userIds, $domain, $credentials);
##############################################################################
function Get-ADUser-For-UserIds {
    param (
        [Parameter(Mandatory=$true)]
        $userIds, 
        [Parameter(Mandatory=$true)]
        $domain, 
        [Parameter(Mandatory=$true)]
        $credentials)

    $results = @{}

    # Loop through each line of the file (one domain ID per line)
    ForEach ($userId in $userIds)
    {
        # Go look up that user and put the results into $result.
        $result = Get-ADUser -Server $domain `
                        -Properties 'SamAccountName' `
                        -Credential $credentials `
                        -Filter { SamAccountName -eq $userId };

        # Add that result to the hash table.
        if ( $result -eq $null)
        {
            $results.Add($userId, $null);
        }
        else
        {
            $results.Add($userId, $result);
        }
    }

    return $results;
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Why would you do this? This helps the consumer of this function with intellisense:

image

and also gives them complete help using the “Get-Help” feature of PowerShell:

image

So, I used this approach to try to make the code a little nicer – you can take your pick from down below. Also, here is a reference for other keywords that are support in those Intellisense comments: http://blogs.technet.com/b/heyscriptingguy/archive/2010/01/07/hey-scripting-guy-january-7-2010.aspx

The source code from this post:
I have two versions of this – a procedural approach, everything in-line (ActiveDirectoryLookup1.ps1) and a modular approach, still procedural script – but with Intellisense and functionality broken into functions (ActiveDirectoryLookup2.ps1):

DOWNLOAD: Complete/working source code for this blog post:
https://gist.github.com/RobSeder/c513891646478574db38

Bottom line:
If you are new to PowerShell, hopefully that helps explains how to do some things. I learned a LOT during this little project. In fact, I’m primarily writing this down so that I can reference it in the future. If you get bogged down with Active Directory requests, maybe you can use some of this to your advantage!

Meanwhile, if you are someone who IS familiar with PowerShell – if you have any comments, corrections, or better ways to do some of these things – please comment below!

Posted in Computers and Internet, Development Tools, General, Infrastructure, PowerShell, Professional Development, Uncategorized
2 comments on “Using PowerShell to query Active Directory
  1. Harry says:

    I installed powershell ver 5 on my windows 7 machine and get-adcomputer -filter * -properties * returns an error. If I try without -properties * it works. Any suggestion

    Error details
    get-adcomputer : One or more properties are invalid.
    Parameter name: msDS-HostServiceAccount
    At line:1 char:1
    + get-adcomputer -filter * -properties *

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Archives
Categories

Enter your email address to follow this blog and receive notifications of new posts by email.

Join 5 other followers

%d bloggers like this: