adpowershellcmdlets
adpowershellcmdlets
The import also creates a new PSDrive, but we won't be using it. However, you
might want to see which commands are in the module:
PS C:\> get-command -module ActiveDirectory
The beauty of these commands is that if I can use a command for one AD
object, I can use it for 10 or 100 or 1,000. Let's put some of these cmdlets to
work.
Let's start with a typical IT pro task: resetting a user's password. We can easily
accomplish this by using the Set-ADAccountPasswordcmdlet. The tricky part is
that the new password must be specified as a secure string: a piece of text
that's encrypted and stored in memory for the duration of your PowerShell
session. So first, we'll create a variable with the new password:
PS C:\> $new=Read-Host "Enter the new password" -AsSecureString
Now we can retrieve the account (using the samAccountname is best) and
provide the new password. Here's the change for user Jack Frost:
PS C:\> Set-ADAccountPassword jfrost -NewPassword $new
Unfortunately, there's a bug with this cmdlet: -Passthru, -Whatif, and -Confirm
don't work. If you prefer a one-line approach, try this:
PS C:\> Set-ADAccountPasswordjfrost -NewPassword
"P@ssw0rd1z3" -force)
Finally, I need Jack to change his password at his next logon, so I'll modify the
account by using Set-ADUser:
PS C:\> Set-ADUserjfrost -ChangePasswordAtLogon $True
The command doesn't write to the pipeline or console unless you use -True. But
I can verify success by retrieving the username via the Get-ADUsercmdlet and
specifying the PasswordExpired property, shown in Figure 2.
The upshot is that it takes very little effort to reset a user's password by using
PowerShell. I'll admit that the task is also easily accomplished by using the
Microsoft Management Console (MMC) Active Directory Users and Computers
snap-in. But using PowerShell is a good alternative if you need to delegate the
task, don't want to deploy the Active Directory Users and Computers snap-in,
or are resetting the password as part of a larger, automated IT process.
Next, let's disable an account. We'll continue to pick on Jack Frost. This code
takes advantage of the
-Whatif parameter, which you can find on many cmdlets that change things, to
verify my command without running it:
PS C:\> Disable-ADAccount jfrost -whatif
OU=staff,OU=Testing,DC=GLOBOMANTICS,DC=local".
When the time comes to enable the account, can you guess the cmdlet name?
PS C:\> Enable-ADAccountjfrost
These cmdlets can be used in a pipelined expression to enable or disable as
many accounts as you need. For example, this code disables all user accounts
in the Sales department:
PS C:\> get-aduser -filter "department -eq 'sales'" |
disable-adaccount
Granted, writing the filter for Get-ADUser can be a little tricky, but that's
where using -Whatif with the Disable-ADAccountcmdlet comes in handy.
Now, Jack has locked himself out after trying to use his new password. Rather
than dig through the GUI to find his account, I can unlock it by using this
simple command:
PS C:\> Unlock-ADAccount jfrost
"CN=Jack Frost,OU=staff,OU=Testing,DC=GLOBOMANTICS,DC=local".
Or I could pipe in a bunch of users and delete them with one simple command:
PS C:\> get-aduser -filter "enabled -eq 'false'"
This one-line command would find and delete all disabled accounts in the
Employees organizational unit (OU) that haven't been changed in at least 180
days.
If you have groups with hundreds of members, then using this command might
be time-consuming; Get-ADGroupMember checks every group. If you can limit
or fine-tune your search, so much the better.
"OU=Groups,OU=Employees,DC=Globomantics,
This command finds all universal groups that don't have any members in my
Groups OU and that display a few properties. You can see the result in Figure
3.
I used a parenthetical pipelined expression to find all users with a City property
of Chicago. The code in the parentheses is executed and the resulting objects
are piped to the -Member parameter. Each user object is then added to the
Chicago Employees group. It doesn't matter whether there are 5 or 500 users;
updating group membership takes only a few seconds This expression could
also be written using ForEach-Object, which might be easier to follow.
PS C:\> Get-ADUser -filter "city -eq 'Chicago'" | foreach
You might want to see who belongs to a given group. For example, you should
periodically find out who belongs to the Domain Admins group:
The cmdlet writes an AD object for each member to the pipeline. But what
about nested groups? My Chicago All Users group is a collection of nested
groups. To get a list of all user accounts, all I need to do is use the -Recursive
parameter:
PS C:\> Get-ADGroupMember "Chicago All Users"
If you want to go the other way -- that is, find which groups a user belongs to --
you can look at the user's MemberOf property:
PS C:\> get-aduserjfrost -property Memberof |
Select -ExpandPropertymemberOf
CN=NewTest,OU=Groups,OU=Employees,
DC=GLOBOMANTICS,DC=local
CN=Chicago Test,OU=Groups,OU=Employees,
DC=GLOBOMANTICS,DC=local
CN=Chicago IT,OU=Groups,OU=Employees,
DC=GLOBOMANTICS,DC=local
DC=GLOBOMANTICS,DC=local
The filter works best with a hard-coded value, but this code will retrieve all
computer accounts that haven't changed their password since January 1, 2012.
You can see the results in Figure 5.
Figure 5: Finding Obsolete Computer Accounts
Another option, assuming that you're at least at the Windows 2003 domain
functional level, is to filter by using the LastLogontimeStamp property. This
value is the number of 100 nanosecond intervals since January 1, 1601, and is
stored in GMT, so working with this value gets a little tricky:
PS C:\> get-adcomputer -filter "LastlogonTimestamp -gt 0"
@{Name="LastLogon";Expression={[datetime]::FromFileTime
($_.Lastlogontimestamp)}},passwordlastset | Sort
LastLogonTimeStamp
To create a filter, I need to convert a date, such as January 1, 2012, into the
correct format, by converting it to a FileTime:
PS C:\> $cutoff=(Get-Date "1/1/2012").ToFileTime()
PS C:\> $cutoff
129698676000000000
* | Select Name,LastlogonTimestamp,PasswordLastSet
This query finds the same computer accounts that I found in Figure 5. Because
there's a random offset with this property, it doesn't matter which approach
you take -- as long as you aren't looking for real-time tracking.
Perhaps when you find those inactive or obsolete accounts, you'd like to disable
them. Easy enough. We'll use the same cmdlet that we use with user accounts.
You can specify it by using the account's samAccountname:
PS C:\> Disable-ADAccount -Identity "chi-srv01$" -whatif
CN=Computers,DC=GLOBOMANTICS,DC=local".
I can also take my code to find obsolete accounts and disable all those
accounts:
PS C:\> get-adcomputer -filter "Passwordlastset
The last task that I'm often asked about is finding computer accounts by type,
such as servers or laptops. This requires a little creative thinking on your part.
There's nothing in AD that distinguishes a server from a client, other than the
OS. If you have a laptop or desktop running Windows Server 2008, you'll need
to get extra creative.
You need to filter computer accounts based on the OS. It might be helpful to
get a list of those OSs first:
PS C:\> Get-ADComputer -Filter * -Properties OperatingSystem |
Figure 8
As with the other AD Get cmdlets, you can fine-tune your search parameters
and limit your query to a specific OU if necessary. All the expressions that I've
shown you can be integrated into larger PowerShell expressions. For example,
you can sort, group, filter, export to a comma-separated value (CSV), or build
and email an HTML report, all from PowerShell and all without writing a single
PowerShell script! In fact, here's a bonus: a user password-age report, saved as
an HTML file:
PS C:\> Get-ADUser -Filter "Enabled -eq 'True' -AND
PasswordLastSet,PasswordNeverExpires,PasswordExpired |
Select DistinguishedName,Name,pass*,@{Name="PasswordAge";
Expression={(Get-Date)-$_.PasswordLastSet}} |sort
Although this one-line command might look intimidating at first, it's pretty
simple to follow when you have a little PowerShell experience. The only extra
step that I took was to define a custom property called PasswordAge. The value
is a timespan between today and the PasswordLastSet property. I then sorted
the results on my new property. Figure 9 shows the output from my little test
domain.
Figure 9
The OU names give PowerShell a place to start its search from, which is called
the search base. The search base typically takes the form of an LDAP
distinguished name (DN)—for example, OU=Marketing, DC=cpandl,DC=com—
so that's what I'll store in my array. The following command creates my array
of OUs to search:
$searchBase =
"OU=Test,DC=cpandl,DC=com",
"OU=Sales,DC=cpandl,DC=com",
"OU=QA,DC=cpandl,DC=com"
(Although this command wraps here, you'd enter it all on one line. The same
holds true for the other commands that wrap.) This command assigns the string
array to the $searchBase variable. If you want to make sure that an array is
created, run the command
$searchBase[0]
Now I need a command that does the searching. For this particular task, I've
chosen the Search-ADAccountcmdlet. The great thing about this cmdlet is that
it has all the parameters I need to search for inactive computer accounts, so I
don't have to build a complex LDAP filter. For example, to find all inactive
computer accounts in a domain, I can simply run
Search-ADAccount
-AccountDisabled -ComputersOnly
Finally, I need a command that moves the disabled computers to a holding OU.
To perform the move, I'll use the Move-ADObjectcmdlet, which lets you easily
move an object or container from one place to another in AD. For example, the
following command uses this cmdlet to move a disabled computer account to
an OU called BitBucket:
Move-ADObject -Identity
"CN=NT4,CN=Computers,DC=cpandl,DC=com"
-TargetPath
"OU=BitBucket,DC=cpandl,DC=com"
In this case, the -Identity parameter specifies the DN of the object I want to
move (a workstation named NT4) and the TargetPath parameter specifies the
DN of the OU I want to move the object to. It's as simple as that.
I now have all the pieces to perform the bulk search-and-move operation: I
have the list of OUs, the command to find disabled computer accounts, and the
command to move them. There are two ways to put them all together,
depending on how comfortable you are with PowerShell.
The limitation of this approach is that I have to type this command for each OU
on my list. This is where my $searchBase array comes in handy. Using that
array, I can write code to iterate through my OU list:
foreach ($ou in $searchBase)
{Search-ADAccount -AccountDisabled
-ComputersOnly -SearchBase $ou |
Move-ADObject -TargetPath
"OU=BitBucket,DC=cpandl,DC=com"}
Let's look at what this code is doing. First, I use the ForEach-Object cmdlet
(aliased to foreach) to iterate through my list of OU names, which is stored in
$searchBase. For each OU in $searchBase, I call the Search-ADAccountcmdlet
and tell it to look for disabled computer accounts in that OU. I then pipe the
output to the Move-Object cmdlet, which moves them. The end result is that all
disabled computer accounts in the three OUs are moved to the BitBucket OU.
The second scenario I want to cover is making mass changes to AD objects. For
example, you might need to modify a particular attribute on a large number of
objects, based on some other criteria. Let's create a scenario, then look at how
to accomplish it.
Suppose I want to find all of the users who are a member of the Marketing
Employees group. For each of these users, I want to write the string FTE to his
or her employeeType attribute.
First, I need to find the best way to determine a user's group memberships. I
have a couple of options:
I could read each user's memberOf attribute to determine whether that user is a direct
member of a particular group. That doesn't necessarily give me any indirect
memberships (groups that are a member of other groups), but it does get me part of
the way there. If I need indirect memberships, I could read a user's tokenGroups
attribute, which is a special constructed attribute that represents both direct and
indirect group memberships. It exists in Windows Server 2003 AD and later.
I could search each group, looking at its members attribute to find out that group's
direct members. I would also need to search through the membership of each group
that's nested within another group. Fortunately, the Active Directory module makes
this task easy. Namely, the Get-ADGroupMembercmdlet provides the -Recursive
parameter, which will chase down any indirect group members.
The second option is a more scalable solution for my scenario, so that's the
approach I'll use.
Next, I need to find the best way to modify attributes on user objects. To do
this, I'll use Set-ADUser. This cmdlet lets you modify properties on user
accounts. It comes with a set of named parameters that include commonly
modified properties. However, you can also modify many other user account
properties by using the generic -Add, -Replace, -Clear, and -Remove
parameters.
Now that I have all the pieces in place to make the bulk change to the
employeeType attribute based on user group membership, let's put it all
together. Once again, I can leverage the PowerShell pipeline, as follows:
Get-ADGroupMember
-Identity "Marketing Employees"
-Recursive |
where { $_.employeeType -eq $null } |
Set-ADUser -Add @{employeeType = "FTE"}
Get-ADGroupMember
-Identity "Marketing Employees"
-Recursive |
where { $_.employeeType -ne $null } |
Set-ADUser -Replace @{employeeType = "FTE"}
Let's look at what these two commands are doing. In the first command, I use
the Get-ADGroupMembercmdlet with the -Recursive parameter to get all the
direct and indirect members of the Marketing Employees group in my domain.
I then use the PowerShell pipeline to send the output to the Where-Object
cmdlet (aliased as where), which checks whether the group member's
employeeType attribute is equal to null (i.e., a value isn't set). If it's null, I pass
the member to the Set-ADUsercmdlet to populate that attribute. Because
employeeType isn't one of Set-ADUser's named parameters and it doesn't
contain a value, I use the -Add parameter to populate it with "FTE". The tricky
thing about using a generic parameter is that you have to pass in a hashtable
that contains the name of the attribute and its value. A hashtable is simply a
key-value pair that you can define by delimiting it with the @{ } construct, as
I've done with @{employeeType = "FTE"}.
The second command is basically the same as the first, except that I change
the where clause to check whether employeeType is not equal to null (i.e.,
already has a value). If it's not null, I pass the member to the Set-
ADUsercmdlet. This time, I use the generic -Replace parameter to change the
existing value to "FTE". Once again, I use a hashtable to provide the new value.
As the two examples show, searching for AD objects is simple with Search-
ADAccount and moving them is effortless with Move-ADObject. Get-
ADGroupMember makes it easy to recursively root out all direct and nondirect
members of a group—a task that used to take quite a bit of work in the pre-
PowerShell days. And when you need to modify those group members, Set-
ADUser quickly gets the job done.
All in all, when it comes to performing bulk operations against AD, the Active
Directory module delivers. Its cmdlets provide a powerful built-in mechanism to
perform most automation tasks. I encourage you to explore the other cmdlets
in the module, which you can enumerate by typing