Tag Archives: ActiveDirectory

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.

Check for Active Directory User in Function Parameters

I’m not sure what it is, but this might’ve been the longest I’ve gone without writing. It’s not that I’ve stopping thinking PowerShell, it’s that I’m so consumed, I’ve had a hard time focusing. My wife calls it my adult ADD. It was just last week that I attended my second PowerShell + DevOps Global Summit (that’s the new name). I learned a great deal and I had a great time. Thanks again to PowerShell.org for the contest and free 4-day pass, my employer for picking up the flight, lodging, and food, my parents for their help, and of course to my wife! The weather in Bellevue, WA was perfect and the summit was a huge success. Much gratitude to all those involved in the planning and execution.

It wasn’t long ago that I had a post about comparing Active Directory group memberships. I wanted a pleasant way to visualize and compare the group memberships of two AD users. We had a ticket earlier today, at the office, where I went back to that post and gathered the commands, so I could use them again. I knew I’d need it, I just didn’t know I’d need it so soon. I sent the output results as an image to a coworker, and it wasn’t but a few seconds later that he was interested in what was used to gather that output: it was my modified compare. Here’s an edited version of the image I sent. This image differs from the original post in that I added the users’ SamAccountNames as the property names in my collection. This is why they’re blurred out this time around.

check-for-active-directory-user-in-function-parameters01

I mentioned to him that I’d write the commands into a wrapper function. While that’s nearly done, I asked myself this question: When should I check if the users exist in Active Directory? Do I do it as part of the function (inside the Begin block, for instance), or do I check at the time the parameter is accepted, as part of a ValidateScript validation?

I’m here today to say that making it a part of the ValidateScript validation attribute is acceptable. While it has the potential to cause an error condition and dump red text to the console, that’s fine. People good at Windows PowerShell read these errors — they’re PowerShell’s errors, and we’re just allowing them to occur. While I can catch these in the function, PowerShell doesn’t always do this for us natively, so I as I see it, it’s not always necessary. Knowing the audience for your tool can help you with this determination. Do you want them to learn PowerShell and get comfortable with the native errors, or does it make sense to protect them from seeing these? It’s up to you. I’m okay with allowing my tools to display some of the native errors, especially since we’re talking about my team using these tools, and not a lower-tiered group. Again, you have to consider who will use your tools, and how they’ll handle the red text.

The test function I wrote, and some error results, can been seen below. In my mind, there were two error possibilities: the computer where the function is invoked doesn’t have the ActiveDirectory module, or one, or both, of the users don’t exist in Active Directory. Again, in many cases, these standard PowerShell errors can be delivered without the need to hide them from the user of the function. Here’s a test function now.

Function Test-ADUser {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true,Position=0)]
        [ValidateScript({Get-ADUser -Identity $_})]
        [string]$User1,

        [Parameter(Mandatory=$true,Position=1)]
        [ValidateScript({Get-ADUser -Identity $_})]
        [string]$User2
    )

    Write-Output -InputObject "Success on both parameters."
}

This first image below, shows the error condition when the ActiveDirectory module isn’t available on the system where the function is invoked. It displays the standard, I-don’t-recognize-what-you-entered error: “Get-ADUser is not recognized…”

check-for-active-directory-user-in-function-parameters02

In this image, we can see the error message that occurs when one of the supplied users doesn’t exist in Active Directory. It won’t get this far unless the ActiveDirectory module is on the system where this function is invoked.

check-for-active-directory-user-in-function-parameters03

So in the end, consider when writing your tools, if a standard PowerShell error is acceptable or not. I think that there’s times it’s suitable, depending on the error and the group using the tool.

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

Get Active Directory (Sites and Services) Subnets

I just started reading chapter 16 — Managing sites and subnets — in Richard Siddaway‘s book Learn Active Directory in a Month of Lunches. I already know a good deal about Active Directory (AD). I’ve been using and supporting it, in one fashion or another, since its introduction in 2000. Even so, that didn’t stop me from picking up this title. I’ll buy and read a book about a topic I already know, even if I’m only going to pick up a few new details. I also like to remind myself of things I already know, in an effort to keep things as fresh as possible in my mind. I’ve also found myself curious about other people’s writing styles. Since I’ve been writing and posting here, for coming up on a year and a half, I believe that my writing has improved, and it makes me eager to read other people’s writings, too.

I went a little off topic there, but anyway, I read a couple pages of the chapter, and then typed the command below into my Windows PowerShell console. This was in an effort to determine the subnets used by an AD site, before reading any further.

PS> (Get-ADReplicationSite -Identity Downtown02 -Properties Subnets).Subnets.ForEach{$_.Split(',',2)[0].Split('=')[-1]}
10.115.0.0/16
10.122.0.0/15
10.140.0.0/16

So what’s happening here? By default, the Active Directory Get-ADReplicationSite cmdlet returns information about your (computer’s) AD site; however, you can filter the results to get information about another AD site, or even, all the AD sites. In my example, I’ve indicated that I only want information about a single site, Downtown02. Although you don’t see it in the default properties that are returned by this cmdlet, there are extended properties returned when the -Properties parameter is included. This behavior is similar to some of the other AD cmdlets. In fact, you can see which AD cmdlets have a -Properties parameter by enter this: Get-Command -Module ActiveDirectory -ParameterName Properties. In our example, we are only returning the Subnets property, not all of the properties, as we would when using -Properties *.

The Subnet property is returned as a Distinguished Name, such as: CN=10.115.0.0/16,CN=Subnets,CN=Sites,CN=Configuration,DC=mydomain,DC=com. Due to this, I’ve added the .Split() method, twice. The first time it was used, we split at the comma and returned two pieces of our string, when it’s entered as .Split(‘,’,2). That’s difficult to explain, so here’s what that looks like:

PS> (Get-ADReplicationSite -Identity Downtown02 -Properties Subnets).Subnets.ForEach{$_.Split(',',2)}
CN=10.115.0.0/16
CN=Subnets,CN=Sites,CN=Configuration,DC=catnet,DC=mydomain,DC=com
CN=10.122.0.0/15
CN=Subnets,CN=Sites,CN=Configuration,DC=catnet,DC=mydomain,DC=com
CN=10.140.0.0/16
CN=Subnets,CN=Sites,CN=Configuration,DC=catnet,DC=mydomain,DC=com

In the example above, notice it only split at the first comma in the string. That’s what the 2 does (“leave me 2 sections”). Adding the [0] index, below, only keeps the first element of the results of each split.

PS> (Get-ADReplicationSite -Identity Downtown02 -Properties Subnets).Subnets.ForEach{$_.Split(',',2)[0]}
CN=10.115.0.0/16
CN=10.122.0.0/15
CN=10.140.0.0/16

The second time we use the .Split() method, we split at the equal sign and return the last element, like so: .Split(‘=’)[-1]. After these two .Split() methods have run, we have our subnets, just like we’d see them in the Name category, of the Active Directory Sites and Services MMC. I should mention the syntax in this command. Take a closer look, if you haven’t already: It’s using the ForEach method, not the ForEach-Object cmdlet, as you might have expected to see. This syntax became possible in PowerShell 4.0.

Okay, so I’ve mentioned this a time or two, now. I often learn something new, share it here, and then I find a better way. Sometime, while I’m still writing. Well, that happened again. We don’t need to get fancy with the results of Get-ADReplicationSite as we have above, and can instead, use the Get-ADReplicationSubnet cmdlet. How’d I find the cmdlet? Well, (while writing), I decided to search for the string “subnet” in all the cmdlet names from the ActiveDirectory module: Get-Command -Module ActiveDirectory -Name *subnet*. Here’s the command to use to return the same information we did with the first example in this post.

PS> (Get-ADReplicationSubnet -Filter * -Properties Site | Where-Object Site -like '*Downtown02*').Name
10.115.0.0/16
10.122.0.0/15
10.140.0.0/16

If you’re wondering why I didn’t use the -Filter parameter to filter on the site, well then, here you go. Here’s a quicker and more efficient method in which to write the command.

PS> (Get-ADReplicationSubnet -Filter {Site -eq 'Downtown02'}).Name
10.115.0.0/16
10.122.0.0/15
10.140.0.0/16

Thanks for joining me, and now back to my reading.

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

Working with an Array of Arrays

There’s a group of servers that I use for a specific project. I can never remember their names or how those correspond to their roles (web front end vs. data back end). Although I’ve updated their Active Directory (AD) descriptions, and created two, specifically named AD groups for them, I wanted an even quicker way to remind myself whose who. With that in mind, I updated my profile with a custom variable that is an array, of arrays.

In this Quick Learn, we’ll work with an example that actually uses three groups of roles — our DCs, our web servers, and our SQL servers. As you’ll soon see, our servers are named after Santa Claus’ reindeer. These names have nothing to do with the role of these servers, and since their names are all closely related, it’s difficult to remember who does what.

This first example, below, demonstrates how we create a new variable, or modify an already existing variable. When we echo the contents of our variable, we get all the computer names, regardless of what array they are in, within the base array. The term ‘base array’ is probably not something you’ll hear or read about outside this post. It’s being used here to help distinguish the array that holds all the other arrays — the container array.

PS C:\> Set-Variable -Name Computers -Value @(('dasher','vixen','cupid'),('comet','dancer','donner'),('blitzen','rudolph','prancer'))
PS C:\> $Computers
dasher
vixen
cupid
comet
dancer
donner
blitzen
rudolph
prancer

We can use an index to return one of the arrays within the base array. In the examples below, you can see how each can be accessed. This is probably a good time to review indexes: The first item in an array is index zero, the second item is index one, the third item is index two, and so on.

PS C:\> $Computers[0]
dasher
vixen
cupid
PS C:\> $Computers[1]
comet
dancer
donner
PS C:\> $Computers[2]
blitzen
rudolph
prancer

In the following example, we can use two indexes to access a specific server. The first index represents which array (within the base array) I want to return, like it did above, and the second index indicates which server I want to return.

PS C:\> $Computers[0][2]
cupid
PS C:\> $Computers[1][0]
comet
PS C:\> $Computers[2][1]
rudolph

The difficult part is going to be able to remember which index is for the DCs, the web servers, or the SQL servers. In that case, I’ll create three more variables to use in place of those index integers.

PS C:\> Set-Variable -Name DCs -Value 0
PS C:\> Set-Variable -Name Web -Value 1
PS C:\> Set-Variable -Name SQL -Value 2
PS C:\> $DCs,$Web,$SQL
0
1
2

With the combination of my $Computers variable and the three, role-specific variables, I am able to easily return the set of servers I want.

PS C:\> $Computers[$DCs]
dasher
vixen
cupid
PS C:\> $Computers[$Web]
comet
dancer
donner
PS C:\> $Computers[$SQL]
blitzen
rudolph
prancer

Now that we have this all figured out, I can use them in different commands. Here’s a couple examples:

PS C:\> $Computers[$SQL] | ForEach-Object {Test-Connection $_ -Count 1}

Source        Destination     IPV4Address      IPV6Address                              Bytes    Time(ms)
------        -----------     -----------      -----------                              -----    --------
TOMMYMS PC... blitzen         10.10.10.80                                               32       1
TOMMYMS PC... rudolph         10.10.10.81                                               32       1
TOMMYMS PC... prancer         10.10.10.82                                               32       1

PS C:\> Get-Service -ComputerName ($Computers[$DCs]) -Name *bit* | Select-Object MachineName,Name,Status | Format-Table -AutoSize

MachineName Name  Status
----------- ----  ------
dasher      BITS Stopped
vixen       BITS Running
cupid       BITS Stopped

The unfortunate thing about hard coding computer names in our profile, is that we’ll run into problems when new servers are added and old ones are decommissioned. Therefore, we’re going to use AD groups — something I mentioned earlier — to populate our array variable. We’ll pull our DCs from the Domain Controllers, and our web servers and SQL servers from two fictions AD groups: WebServers and SQLBoxes. Here’s the command we’ll add to our profile to ensure we always have the correct server names. While this can all be on a single line command I’ve added line breaks to give it an easier-to-read appearance.

Set-Variable -Name Computers -Value @(
    ((Get-ADDomainController -Filter *).Name),
    ((Get-ADGroupMember -Identity WebServers).Name),
    ((Get-ADGroupMember -Identity SQLBoxes).Name)
)

And, that’s it. If for some reason you’re using PowerShell 2.0 or lower (and I really hope you’re not), you’ll need to include Import-Module -Name ActiveDirectory in your profile. As well — and it should go without saying — you’ll need to be working from a computer that actually has the ActiveDirectory module installed. Although I’m using PowerShell 4.0, I still include Import-Module -Name ActiveDirectory in my profile, so I don’t have to wait for it to auto load, when I run my first AD cmdlet.