Tag Archives: Get-ADComputer

Handle a Parameter Whether it’s Included or Not

I’m in the process of writing a new tool. It’s basically a wrapper function for Get-ADObject that only returns Active Directory (AD) contact objects. While there’s a Get-ADComputer cmdlet to get AD computer objects, and a Get-ADUser cmdlet to get AD user objects, there’s no specific cmdlet for contacts. There’s no surprise here really, and that’s likely why we have a Get-ADObject cmdlet. It’s for those AD objects that didn’t make the cut and get their own.

I’ve seen some discussion on this in the past: How do I handle running a cmdlet within a function when I don’t know if a parameter and parameter value will be included, or not? Let’s consider that my Get-ADContact function will run the Get-ADObject cmdlet, as seen below. In the Begin block, we create a $Parameter variable that will hold a hash table. We’ll populate it in such a way that the key Filter will have a corresponding value of {ObjectClass -eq ‘contact’}. In the Process block, we splat this hash table, currently with a single parameter and parameter value, to the Get-ADObject cmdlet.

Function Get-ADContact {
    [CmdletBinding()]
    Param (
    )

    Begin {
        # Create parameter hash table; add Filter parameter.
        $Parameters = @{Filter = {ObjectClass -eq 'contact'}}
    } # End Begin.

    Process {
        Get-ADObject @Parameters
    } # End Process.

    End {
    } # End End.
} # End Function: Get-ADContact.

There’s two other parameters included in Get-ADObject that I want to allow my users the ability to include. That’s the -SearchBase and -SearchScope parameters. You can read more by checking the help for Get-ADObject: Get-Help -Name Get-ADObject. There’s actually several AD cmdlets that also include these parameters. They can be quite helpful, so that’s why I’ve decided to include them.

Before we continue, I want to let the audience know that I am familiar with creating proxy functions, and I understand it might’ve been a better option. Maybe I’ll circle back sometime and see about replicating the functionality I’ve created here, in that manner. It might turn out this wasn’t worth writing and posting. No guarantee, but it’s possible.

Okay, back on track. Let’s add the additional lines inside the Param block, that make accepting a -SearchBase and -SearchScope parameter value possible.

Function Get-ADContact {
    [CmdletBinding()]
    Param (
        [Parameter()]
        [string]$SearchBase,

        [Parameter()]
        [ValidateSet(0,'Base',1,'OneLevel',2,'SubTree')]
        $SearchScope
    )

    Begin {
        # Create parameter hash table; add Filter parameter.
        $Parameters = @{Filter = {ObjectClass -eq 'contact'}}
    } # End Begin.

    Process {
        Get-ADObject @Parameters
    } # End Process.

    End {
    } # End End.
} # End Function: Get-ADContact.

Now, our Get-ADContact function will include the two additional parameters. Neither parameter is mandatory, but the -SearchScope parameter does include a ValidateSet parameter attribute to ensure it’ll only accept the values 0, 1, 2, Base, OneLevel, or SubTree. Base and 0 are equivalent, as are 1 and OneLevel, and 2 and SubTree.

The next thing I need to do is include the parameter values assigned to the -SearchBase and -SearchScope parameters to our $Parameters hash table when those are included. I decided to do this using the $PSBoundParameters variable, the ForEach-Object cmdlet, and the switch language construct.

Function Get-ADContact {
    [CmdletBinding()]
    Param (
        [Parameter()]
        [string]$SearchBase,

        [Parameter()]
        [ValidateSet(0,'Base',1,'OneLevel',2,'SubTree')]
        $SearchScope
    )

    Begin {
        # Create parameter hash table; add Filter parameter.
        $Parameters = @{Filter = {ObjectClass -eq 'contact'}}

        $PSBoundParameters.Keys | ForEach-Object {
            Switch ($_) {
                'SearchBase' {$Parameters += @{SearchBase = $SearchBase}; break}
                'SearchScope' {$Parameters += @{SearchScope = $SearchScope}}
            }
        }
    } # End Begin.

    Process {
        Get-ADObject @Parameters
    } # End Process.

    End {
    } # End End.
} # End Function: Get-ADContact.

All done. Now, we have function that only returns AD contact objects. Additionally, we have the option of narrowing down our search by including the often used -SearchBase and -SearchScope parameters. While I don’t doubt there’s a better way, I think this one will work for now.

Format PowerShell Results for Outside of PowerShell

There are times I use PowerShell to help format information I need to send along to others in an email. What I mean is that I return results from PowerShell commands, and format it using PowerShell, so that I can simply send it to the clipboard and paste it into an email.

The below example gathers all the computer-related details about my Lync servers each time I open a new Windows PowerShell session. The time to complete is minimal, so I’m perfectly okay with returning all the properties on each of the servers. The extra milliseconds are worth having the most current information on these servers inside my $Lync variable. By the way, you don’t need to know anything about Lync (or Skype) to make use of the post; it’s not the point.

PS > $Lync = Get-ADComputer -Filter * -SearchBase "OU=Lync,DC=MyDomain,DC=com" -Properties *

With the variable set and assigned, I can do things like the next two, combined examples. This comes in handy all the time, and without the need to think about rewriting the command whenever it’s needed again.

PS > $Lync.Name
L-FE01
L-PC02
L-PC01
L-FE02
L-Ed01
L-FE04
L-FE03
L-Ed02

PS > $Lync | Select-Object Name,Description

Name              Description
----              ----------- 
L-FE01            Lync 2013 Front End
L-PC02            Lync 2013 Persistent Chat
L-PC01            Lync 2013 Persistent Chat
L-FE02            Lync 2013 Front End
L-Ed01            Lync 2013 Edge
L-FE04            Lync 2013 Front End
L-FE03            Lync 2013 Front End
L-Ed02            Lync 2013 Edge

Let’s consider that I need to enter the names of the servers into an email and I want them to be comma separated. Easy, we’ll the use the -join operator to complete this task

PS > $Lync.Name -join ','
L-FE01,L-PC02,L-PC01,L-FE02,L-Ed01,L-FE04,L-FE03,L-Ed02
PS >
PS > # Humm... let's add spaces, too.
PS >
PS > $Lync.Name -join ', ' # <-- Notice the trailing space.
L-FE01, L-PC02, L-PC01, L-FE02, L-Ed01, L-FE04, L-FE03, L-Ed02

Because I’m sold on PowerShell, I’ll always take extra time to use it to its full potential. What I wanted to do was add the word “and” after the last comma and a space, and before the name of the final Lync server. This will make the most sense when my text is dropped into an email and used as, or part of, a sentence. We’ll start this example by determining the location of the last comma by using the .LastIndexOf() method. This returns the location within the string.

PS > $index = (($Lync.Name) -join ', ').LastIndexOf(',')
PS > $index
54

Now that we know the location of the last comma, we can remove it and then insert what we want. The next example uses two methods. First the .Remove() method removes the comma, and then the .Insert() method adds everything the way we want it.

PS > (($Lync.Name) -join ', ').Remove($index,1).Insert($index,', and')
L-FE01, L-PC02, L-PC01, L-FE02, L-Ed01, L-FE04, L-FE03, and L-Ed02
PS >
PS > (($Lync.Name) -join ', ').Remove($index,1).Insert($index,', and') | clip.exe

In the last above line, we reran the command and piped it to clip, so that it’s ready to be pasted into my email. After you do this awhile, you find little ways in PowerShell to handle the exact formatting you want. It’s these little tasks, that will give you an opportunity to continue to practice your PowerShell. And finally, here’s the email where I entered the information I had collected and formatted in PowerShell.

format-powershell-results-for-outside-of-powershell-01

Prep computers.txt File for HTA

Last week, I opted to share a couple of old HTAs I had written. HTAs are HTML Applications and allow administrations the ability to create a graphical interface for their scripts. It’s an older technology and not something I still write, or use. Even so, I wanted to share them with the community. As they’re not Windows PowerShell-related, I thought I should circle back on that post and incorporate some PowerShell.

The second of the two HTAs I shared was called Remote Desktop Assistant (download link: RDAssistantv2.1 (311 downloads) ). Its purpose is to allow a user to select a computer description from a list and open Remote Desktop to connect to that computer. I know, I know, this goes against all things PowerShell, but it was written a long time ago. The HTA has a requirement for an external text file called computers.txt that stores computer descriptions and computer names / IP addresses, such as we have in the list below. It’s a computer description, a semi-colon, and the computer name or IP address.

computer1;10.10.10.5
computer2;dns1.mydomain.com
computer3;10.10.10.9

We can make use of Active Directory and PowerShell to create this list and subsequent text file, so we don’t have to do it manually. In fact, once you had your code written you could schedule it to ensure the computers.txt file was as accurate as the last time the scheduled task ran. While the preferred way to do this would be to store host names (at least in my opinion), I’ll show examples of collecting the IP addresses, too.

In the first example, we’ll pull all of our servers from a single Organizational Unit to return the names and DNSHostNames.

PS> Get-ADComputer -Filter * -SearchBase 'OU=Servers,DC=mydomain,DC=com' -Properties DNSHostName | Select-Object Name,DNSHostName

Name                                                        DNSHostName
----                                                        -----------
SQL01                                                       SQL01.mydomain.com
SQL02                                                       SQL02.mydomain.com
SQL03                                                       SQL03.mydomain.com
SQL04                                                       SQL04.mydomain.com

In this example, we return the names and IP addresses.

PS> Get-ADComputer -Filter * -SearchBase 'OU=Servers,DC=mydomain,DC=com' -Properties IPv4Address | Select-Object Name,IPv4Address

Name                                                        IPv4Address
----                                                        -----------
SQL01                                                       10.10.10.30
SQL02                                                       10.10.10.31
SQL03                                                       10.10.10.32
SQL04                                                       10.10.10.33

While these are the results we want, we need to get them into the proper format. We’ll do this by looping through each result and concatenating the two properties with semi-colon in between. To do this, we do not need to use the Select-Object cmdlet to return the Name and IPv4Address, or DNSHostName.

PS> Get-ADComputer -Filter * -SearchBase 'OU=Servers,DC=mydomain,DC=com' -Properties IPv4Address | ForEach-Object {"$($_.Name):$($_.IPv4Address)"}
SQL01;10.10.10.30
SQL02;10.10.10.31
SQL03;10.10.10.32
SQL04;10.10.10.33

With the host name set, we’ll take this one step further and create our computers.txt file.

PS> Get-ADComputer -Filter * -SearchBase 'OU=Servers,DC=mydomain,DC=com' -Properties DNSHostName | ForEach-Object {"$($_.Name):$($_.DNSHostName)"} | Out-File -FilePath C:\computers.txt
PS> Get-Content -Path C:\computers.txt
SQL01;SQL01.mydomain.com
SQL02;SQL02.mydomain.com
SQL03;SQL03.mydomain.com
SQL04;SQL04.mydomain.com

Chances are good that if you use the DNSHostName, you’re never going to have an Active Directory computer object returned without one. The same can’t be said if you use the IPv4Address, as this property is created at the time the results are returned (it queries DNS). Think about it, have you ever seen an IPv4Address property inside Active Directory Users and Computer when viewing a computer object? The DNSHostName option might be the better option, but I’ll leave that up to you.

Only Return System.DateTime Properties from Get-ADComputer

I had one of those randomly appearing PowerShell questions last night. Windows PowerShell is a huge interest for me, so there’s really no surprise.

I wondered, How can only return the date time related properties from Get-ADComputer? It seems to happen quite often that I’ll need to view date and time information from Get-ADComputer (and Get-ADUser). It’s mildly cumbersome to scan though all the properties looking for dates and times in the property’s value — the ultimate reason behind this random thought.

The command I threw together was ugly, and seemed to lack a simpler approach. I stopped there, and decided to pass this one out to the community. Can you come with a better way?

Again, the idea is to return only the properties from Get-ADComputer that are System.DateTime properties (have System.DateTime in the Definition property of Get-Member). Take a look at my example and you might better understand my goal. I didn’t bother filtering out the default properties returned by Get-ADComputer (at first), but you’re welcome to do that, too. Cheers!

PS> Get-ADComputer -Identity SERVER01 -Properties ((Get-ADComputer -Identity SERVER01 -Properties * | Get-Member | Where-Object Definition -match 'System.DateTime').Name)

AccountExpirationDate  :
AccountLockoutTime     :
Created                : 9/12/2013 1:34:06 PM
createTimeStamp        : 9/12/2013 1:34:06 PM
DistinguishedName      : CN=SERVER01,OU=Finance,DC=mydomain,DC=com
DNSHostName            : SERVER01.mydomain.com
Enabled                : True
LastBadPasswordAttempt :
LastLogonDate          : 3/17/2014 10:35:35 AM
Modified               : 8/18/2014 11:48:34 AM
modifyTimeStamp        : 8/18/2014 11:48:34 AM
Name                   : SERVER01
ObjectClass            : computer
ObjectGUID             : 234cbaed59-1ab3-6ebc-9782-e9542bedaec
PasswordLastSet        : 3/14/2014 5:12:24 PM
SamAccountName         : SERVER01$
SID                    : S-1-5-21-174985627-956854884-123956358-942569
UserPrincipalName      :
whenChanged            : 8/18/2014 11:48:34 AM
whenCreated            : 9/12/2013 1:34:06 PM

Update1: I went ahead and edited the command so that it would not return the default Get-ADComputer properties (Name, SamAccountName, etc.), using the Select-Object cmdlet. I repeated the command I issued to the -Properties parameter of Get-ADComputer, as the value for Select-Object.

So, any takers? Can you come up with a better way to do this?

PS> Get-ADComputer -Identity SERVER01 -Properties ((Get-ADComputer -Identity SERVER01 -Properties * | Get-Member | Where-Object Definition -match 'System.DateTime').Name) | Select-Object (Get-ADComputer -Identity SERVER01 -Properties * | Get-Member | Where-Object Definition -match 'System.DateTime').Name

AccountExpirationDate  :
AccountLockoutTime     :
Created                : 9/12/2013 1:34:06 PM
createTimeStamp        : 9/12/2013 1:34:06 PM
LastBadPasswordAttempt :
LastLogonDate          : 3/17/2014 10:35:35 AM
Modified               : 8/18/2014 11:48:34 AM
modifyTimeStamp        : 8/18/2014 11:48:34 AM
PasswordLastSet        : 3/14/2014 5:12:24 PM
whenChanged            : 8/18/2014 11:48:34 AM
whenCreated            : 9/12/2013 1:34:06 PM

Update2: There’s a second update to this post. Jonathan Angliss tweeted a much cleaner solution — exactly what I had hoped someone might do. Here’s his contribution:

PS> Get-ADComputer -Identity SERVER01 -Properties * | ForEach-Object {$_.psobject.properties | Where-Object {$_.TypeNameofValue -eq 'System.DateTime'}} | Select-Object Name,Value

Report on Active Directory Objects in Abandoned Organizational Unit

Before we really start this post, I should mentioned that there’s no reason that the script discussed in this post can’t be run against an Organizational Unit (OU) that hasn’t been abandoned. It just worked out that I wrote the script in order to determine if an OU had be abandoned.

I threw a small script together in the last couple days and thought I’d share it. The reason for the script was because I may have had an Active Directory (AD) OU that was no longer being used. In order to determine if this was really the case, I wanted to check various properties on the user and computer objects in the OU, to include any nested OUs. These properties included the last logon time stamp, the last date the objects changed, and a few others.

The first couple of lines in the script set two different variables. The first one stores the Domain’s Distinguished Name, and the second one is assigned the location of the abandoned OU. The second variable is based partly on the first. This script requires the ActiveDirectory module and assumes it’s being run on PowerShell 3.0 or greater, as the AD module isn’t explicitly imported.

$DomainDN = ((Get-ADDomain).DistinguishedName)
$AbandonedOU = Get-ADObject -Filter * -SearchBase "OU=Finance,OU=Departments,$DomainDN"

In the next part of the script, we start to send the $AbandonedOU variable’s objects across the pipeline, to the Foreach-Object cmdlet. As each object passes across, we determine what type of AD object we’re dealing with. If it’s a user object, we set the $Command variable to the string, Get-ADUser. If it’s a computer object we set the $Command variable to the string, Get-ADComputer. If it’s neither, such as a nested OU, we’ll return to the $AbandonedOU variable and send the next object without assigning anything to the $Command variable (or running any of the upcoming code).

$AbandonedOU | ForEach-Object {
    If ($_.ObjectClass -eq 'user') {
        $Command = 'Get-ADUser'
    } ElseIf ($_.ObjectClass -eq 'computer') {
        $Command = 'Get-ADComputer'
    } Else {
        return
    }

Providing we have a user or computer AD object, we’ll run the code in the next example. This will execute the cmdlet, whether it be Get-ADUser or Get-ADComputer, returning the requested properties that we then calculate (think, customize).

    & $Command -Identity $_ -Properties * |
        Select-Object Name,
            @{N='Type';E={$_.ObjectClass}},
            @{N='Created';E={$_.whenCreated}},
            @{N='Last Logon TimeStamp';E={[datetime]::FromFileTime($_.LastLogonTimeStamp)}},
            @{N='Changed';E={$_.whenChanged}},
            @{N='Added To Domain By';E={$_.nTSecurityDescriptor.Owner}}
}

Finally, we sort the collection of objects we’ve returned and customized, and in my case, pump the data out to a CSV file at the root of my C:\ drive. As you’ll see below, I’ve included both the code in the previous example and the additional code.

    & $Command -Identity $_ -Properties * |
        Select-Object Name,
            @{N='Type';E={$_.ObjectClass}},
            @{N='Created';E={$_.whenCreated}},
            @{N='Last Logon TimeStamp';E={[datetime]::FromFileTime($_.LastLogonTimeStamp)}},
            @{N='Changed';E={$_.whenChanged}},
            @{N='Added To Domain By';E={$_.nTSecurityDescriptor.Owner}}
} | Sort-Object 'Last Logon TimeStamp' -Descending | Export-Csv -Path C:\AbandonedOU.csv -NoTypeInformation

I want to mention something about the line above that calculates the “Added To Domain By” property. In many environments this is going to only be <DOMAIN>\Domain Admins. The reason I added this, is because in the AD environment in which this ran, users, other than the Domain Admins, can join computers. I know this is a default; however, in many environments it is not allowed. This may or may not be a helpful property in your environment.

Cheers, and thanks for reading! I’ve included the complete script below.

$DomainDN = ((Get-ADDomain).DistinguishedName)
$AbandonedOU = Get-ADObject -Filter * -SearchBase "OU=Finance,OU=Departments,$DomainDN"

$AbandonedOU | ForEach-Object {
    If ($_.ObjectClass -eq 'user') {
        $Command = 'Get-ADUser'
    } ElseIf ($_.ObjectClass -eq 'computer') {
        $Command = 'Get-ADComputer'
    } Else {
        return
    }

    & $Command -Identity $_ -Properties * |
        Select-Object Name,
            @{N='Type';E={$_.ObjectClass}},
            @{N='Created';E={$_.whenCreated}},
            @{N='Last Logon TimeStamp';E={[datetime]::FromFileTime($_.LastLogonTimeStamp)}},
            @{N='Changed';E={$_.whenChanged}},
            @{N='Added To Domain By';E={$_.nTSecurityDescriptor.Owner}}
} | Sort-Object 'Last Logon TimeStamp' -Descending | Export-Csv -Path C:\AbandonedOU.csv -NoTypeInformation

Using OutVariable — Why Don’t I Do that More Often?

This week, Microsoft Virtual Academy had two live events about DSC (Desired State Configuration), hosted by Jeffery Snover and Jason Helmick. I watched as much as I was able, but there were some problems at work that demanded my attention, and so I was grudgingly pulled away from a good portion of both sessions. Luckily for me, and for you, if you missed them, is that the videos should be up in the next two to three weeks. That will allow anyone who is interested the ability to move through the modules (think sections, not PowerShell modules) around other ongoing tasks — like work.

I didn’t start this post to discuss DSC, but instead because of what I watched Jeffery Snover do several times. While I’ve always been aware of the existence of the -OutVariable common parameter, I’m not even sure if I’ve ever used it or not (although I’m certain I’ve used -ErrorVariable). This parameter is a great way to view your command’s results immediately, and write them to a variable at the same time.

In this example, we return the computers’ names from (all of) Active Directory (AD) that have the word ‘physical’ somewhere inside their description property. The problem here is that if we need to generate this list a second time, we’ll have to run the command again. This can be resource intensive, depending on the command, and not inline with best practice — at least, my best practice.

PS C:\> (Get-ADComputer -Filter {Description -like '*physical*'}).Name
DC01
DC02
DC03
WEB01
WEB02

In this example, we write our results to the variable $Physical. The difference here is that we don’t write the results to the screen automatically, but only when we echo the variable’s contents.

PS C:\> $Physical = (Get-ADComputer -Filter {Description -like '*physical*'}).Name
PS C:\> $Physical
DC01
DC02
DC03
WEB01
WEB02

In this example, we combine the best of both worlds: instant results written to the screen, with the “same” values stored in a variable. Notice that when I echo the variable, $P, it returns more than just the Name. This is because all the properties were written to the variable, before we displayed only the Name property. Note: I’ve concatenated the results after the first computer’s full results.

PS C:\> (Get-ADComputer -Filter {Description -like '*physical*'} -OutVariable P).Name
DC01
DC02
DC03
WEB01
WEB02
PS C:\> $P
DistinguishedName : CN=DC01,OU=Domain Controllers,DC=mydomain,DC=com
DNSHostName       : dc01.mydomain.com
Enabled           : True
Name              : DC01
ObjectClass       : computer
ObjectGUID        : ...
SamAccountName    : DC01$
SID               : ...
UserPrincipalName :
...

Here’s how we can return only the Name property, using this variable.

PS C:\> $P.Name
DC01
DC02
DC03
WEB01
WEB02

Hopefully I can remember to use this common parameter more often. We’ve been taught to store our results in a variable, so we aren’t continually performing resource intensive queries. This is a great way to do that, with the option to have the results of a command written to the screen immediately. Adios, friends.