Monthly Archive


PowerShell and Active Directory

Get-ADUser filtering

Saw a question on the forums that revolved around Get-ADUser filtering.

Initial code was like this

Import-Csv .\users.txt |
foreach {
  Get-ADUser -Filter {Name -like $_.Name}

which on the face of it seems reasonable. However, you get errors like this

Get-ADUser : Property: 'Name' not found in object of type:
At line:3 char:3
+   Get-ADUser -Filter {Name -like $_.Name}
+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Get-ADUser], ArgumentException
    + FullyQualifiedErrorId : ActiveDirectoryCmdlet:System.ArgumentException,Microsoft

Change –like to –eq and you’ll still get the error.

This won’t work either:

Import-Csv .\users.txt |
foreach {
  Get-ADUser -Filter {Name -like $($_.Name)}

You get messages about path errors.

Import-Csv .\users.txt |
foreach {
  Get-ADUser -Filter {Name -like "$($_.Name)"}

will run but won’t return any data.

This will run and return the correct data.

Import-Csv .\users.txt |
foreach {
  $name = $_.Name
  Get-ADUser -Filter {Name -like $name}

Alternatively, you can use quotes so that the filter is a string

Import-Csv .\users.txt |
foreach {
  Get-ADUser -Filter "Name -like '$($_.Name)'"

Another option is to use the LDAP filter syntax

Import-Csv .\users.txt |
foreach {
  $name = $_.Name
  Get-ADUser -LDAPFilter "(Name=$name)"

Import-Csv .\users.txt |
foreach {
  Get-ADUser -LDAPFilter "(Name=$($_.Name))"

The help file about_activedirectory_filter is well worth reading. It doesn’t seem to be installed with the AD module on Windows Server 2016. You can read it on line at

You’ll also see links to

about_ActiveDirectory  - overview of module



Get-ADUser filtering isn’t the most obvious of topics to get your head round but these examples should help you make your filters work. If you’re building a complex filter build it up a step at a time so you can test each stage.

Are your domain controllers real?

A question on the forum asked about discovering if domain controllers are physical or virtual machines.

This will do the job

foreach ($domain in (Get-ADForest).domains) {
 Get-ADDomainController -filter * -server $domain |
 sort hostname  |
 foreach {
 Get-CimInstance -ClassName Win32_ComputerSystem -ComputerName $psitem.Hostname |
 select PSComputerName, Manufacturer, Model


Get the domains in your forest and then for each domain get the domain controllers. Get-ADDomainController outputs an object with a property of hostname – but you need a computername for Get-CimInstance. So, use a foreach-object and use the Hostname property as shown (you could create a property ComputerName on the pipeline object but its more work) and get the results. A virtual machine will show under the Model. You can sort or whatever once you have the results.

Name mismatch

Ever wondered why you can’t do this:

Get-ADComputer -Filter * -SearchBase 'OU=Servers,DC=Manticore,DC=org' |
Get-CimInstance -ClassName Win32_OperatingSystem

The –ComputerName parameter on get-CimInstance accepts pipeline input BUT its by property name.

PS> Get-Help Get-CimInstance -Parameter ComputerName

-ComputerName [<String[]>]
Specifies computer on which you want to run the CIM operation. You can specify a fully qualified domain name
(FQDN), a NetBIOS name, or an IP address.

If you do not specify this parameter, the cmdlet performs the operation on the local computer using Component
Object Model (COM).

If you specify this parameter, the cmdlet creates a temporary session to the specified computer using the WsMan

If multiple operations are being performed on the same computer, using a CIM session gives better performance.

Required?                    false
Position?                    named
Default value                none
Accept pipeline input?       True (ByPropertyName)
Accept wildcard characters?  false

If you look at the output of Get-ADComputer it has a Name property.

PS>  Get-ADComputer -Filter * -SearchBase 'OU=Servers,DC=Manticore,DC=org'

DistinguishedName : CN=W16PWA01,OU=Servers,DC=Manticore,DC=org
DNSHostName       :
Enabled           : True
Name              : W16PWA01
ObjectClass       : computer
ObjectGUID        : 8d137004-1ced-4ff1-bcf4-f0671652fc8c
SamAccountName    : W16PWA01$
SID               : S-1-5-21-759617655-3516038109-1479587680-1322
UserPrincipalName :

So you have a Name mismatch between the property and the parameter.

There are a number of ways to deal with this.

First use foreach

Get-ADComputer -Filter * -SearchBase 'OU=Servers,DC=Manticore,DC=org' |
foreach {
Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $psitem.Name |
select CSName, Caption

Use $psitem.Name (or $_.Name) as the input to –ComputerName. Simple coding and works very nicely.

If you have a lot of computers you may want to use a foreach loop instead

$computers = Get-ADComputer -Filter * -SearchBase 'OU=Servers,DC=Manticore,DC=org' | select -ExpandProperty Name
foreach ($computer in $computers) {
Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $computer|
select CSName, Caption

Create an array of computer names and iterate through them.

Second use select

Get-ADComputer -Filter * -SearchBase 'OU=Servers,DC=Manticore,DC=org' |
select @{N='ComputerName';E={$_.Name}} |
Get-CimInstance -ClassName Win32_OperatingSystem |
select CSName, Caption

In this case use select-object to create a property with the name ComputerName (case DOESN’T matter) and pipe that into Get-CimInstance.

This option is a bit more advanced as you have understand how select-object works and how to create extra properties on the object you’re passing down the pipeline. It looks cooler and should get you a few extra “ace powerShell coder” points.

The third option takes advantage of the fact that _Computername accepts an array of computer names

Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName (Get-ADComputer -Filter * -SearchBase 'OU=Servers,DC=Manticore,DC=org' | select -ExpandProperty Name) |
select CSName, Caption

You run Get-ADComputer and use select –Expand to only return the VALUE of the computer name (a string). This gives you an array of computer names. Because its in () its treated as an input object to the parameter.

Very clever and gets you maximum points.

Modifying AD users in bulk

Modifying AD users in bulk involves either setting one or more properties to the same value for a set of users or reading in the values you need from a data source of some kind.

We prepared some test data in the last post so lets see how we use it.

$users = Import-Csv -Path .\users.csv
foreach ($user in $users){
Get-ADUser -Identity $user.Id |
Set-ADUser -Division $user.Division -EmployeeNumber $user.EmployeeNumber

The simplest way is to read in the data and store as a collection of objects. Use foreach to iterate through the set of user information. Get-ADUser gets the appropriate AD account which is piped to Set-ADUser. Set-ADUser is a great cmdlet because it has parameters for most of the user properties.

In this case though we know that some of the users don’t have employee numbers. This means a bit more work. Two approaches are possible – use splatting and the parameters used above or use the –Replace option

Lets look at splatting first

$users = Import-Csv -Path .\users.csv
foreach ($user in $users){
$params = @{
Division = $user.Division
EmployeeNumber = 0

if ($user.EmployeeNumber) {
$params.EmployeeNumber = $user.EmployeeNumber
else {

Get-ADUser -Identity $user.Id |
Set-ADUser @params

As before read the user information into the $users variable. Iterate over the users with foreach. Create a hashtable for the parameters and their values. Division is always present so that can be set directly. Employeenumber should be tested and if  present the place holder value should be overwritten with the correct value otherwise Employeenumber is removed from the hashtable.

The user account is found and Set-ADUser sets the correct values. Notice how the hashtable is specified to the cmdlet.

Splatting is a great way to dynamically set the parameters you’re using on a particular cmdlet.

Set-ADUser has an alternative – the –Replace parameter.

$users = Import-Csv -Path .\users.csv
foreach ($user in $users){
$params = @{
division = $user.Division
employeeNumber = 0

if ($user.EmployeeNumber) {
$params.EmployeeNumber = $user.EmployeeNumber
else {

Get-ADUser -Identity $user.Id |
Set-ADUser -Replace $params

This is very similar to the splatting example but instead of splatting the hashtable you use it as the value input to the Replace parameter. If you wrote  out the command it would look like this:

Set-ADUser –Replace @{division = ‘Division B’; employeeNumber  = 100}

With –Replace you’re using the LDAP names of the properties rather than the GUI or PowerShell name – there are differences for instance surname is sn in LDAP.

Modifying AD users in bulk is straightforward with PowerShell and its relatively easy to deal with missing values if you adopt one of the above ideas. Splatting is probably the easiest in this case.

Test data for bulk AD changes

I’ve had a number of questions about changing AD user data in bulk. If you need to do that you need some test data. The specific questions were around setting the Division property and the EmployeeNumber at the same time – but some accounts didn’t have an employee number.

First you need to generate some test data

$count =  1

Get-ADUser -Filter * -SearchBase 'OU=UserAccounts,DC=Manticore,DC=org' |
foreach {

$props = @{
Id = $psitem.SamAccountName
Division = ''
EmployeeNumber = $count
switch ($count % 3){
0 {$props.Division = 'Division A'}
1 {$props.Division = 'Division B'}
2 {$props.Division = 'Division C'}

if (-not ($count % 15)){$props.Remove('EmployeeNumber')}

New-Object -TypeName PSObject -Property $props

$count ++
} |
Export-Csv -Path users.csv –NoTypeInformation

You need a counter variable. Use Get-ADUser to get you test users and pipe to foreach. You can then create a hashtable for the properties – Id = samaccountname, Division is an empty string and EmployeeNumber is set equal to count.

A switch statement is used to modify the division. Modulo 3 on the $count variable can only give values of 0, 1 or 2.

Remove the EmployeeNumber for every 15th account

Create an object. Increment the counter.

Export the objects to a csv file.  You should end up with something like this:

Division   EmployeeNumber Id
--------   -------------- --
Division B 1              DonJones
Division C 2              DonSmith
Division A 3              DonBrown
Division B 4              DonBlack
Division C 5              DonWhite
Division A 6              DonGreen
Division B 7              DonWood
Division C 8              DonBell
Division A 9              DonHarris
Division B 10             DonFox
Division C 11             JamesJones
Division A 12             JamesSmith
Division B 13             JamesBrown
Division C 14             JamesBlack
Division A                JamesWhite
Division B 16             JamesGreen
Division C 17             JamesWood


Next time I’ll show you how to deal with the missing employee numbers when you modify the AD accounts.

Get-ADUser doesn’t display all properties

Microsoft’s Active Directory cmdlets have some issues. One of the ones that catches everyone when they start using them is that Get-ADUser doesn’t display all properties.

A default call to Get-ADUser displays a subset of the available properties of the user object:

DistinguishedName : CN=FOX Fred,OU=UserAccounts,DC=Manticore,DC=org
Enabled           : True
GivenName         :
Name              : FOX Fred
ObjectClass       : user
ObjectGUID        : db5a3975-980d-4749-b9c0-48aff9217b2a
SamAccountName    : foxfred
SID               : S-1-5-21-759617655-3516038109-1479587680-1314
Surname           :
UserPrincipalName :


Even if the properties are empty – such as Givenname and Surname – the property name is displayed. So, how do you get the properties that aren’t part of the default list?

There’s the brute force approach:

PS> Get-ADUser -Identity foxfred -Properties *
AccountExpirationDate                :
accountExpires                       : 9223372036854775807
AccountLockoutTime                   :
AccountNotDelegated                  : False
AllowReversiblePasswordEncryption    : False
AuthenticationPolicy                 : {}
AuthenticationPolicySilo             : {}
BadLogonCount                        : 0
badPasswordTime                      : 0
badPwdCount                          : 0
CannotChangePassword                 : False
CanonicalName                        : Fred
Certificates                         : {}
City                                 :
CN                                   : FOX Fred
codePage                             : 0
Company                              :
CompoundIdentitySupported            : {}
Country                              :
countryCode                          : 0
Created                              : 17/11/2016 14:07:13
createTimeStamp                      : 17/11/2016 14:07:13
Deleted                              :
Department                           :
Description                          :
DisplayName                          :
DistinguishedName                    : CN=FOX Fred,OU=UserAccounts,DC=Manticore,DC=org
Division                             :
DoesNotRequirePreAuth                : False
dSCorePropagationData                : {01/01/1601 00:00:00}
EmailAddress                         :
EmployeeID                           :
EmployeeNumber                       :
Enabled                              : True
Fax                                  :
GivenName                            :
HomeDirectory                        :
HomedirRequired                      : False
HomeDrive                            :
HomePage                             :
HomePhone                            :
Initials                             :
instanceType                         : 4
isDeleted                            :
KerberosEncryptionType               : {}
LastBadPasswordAttempt               :
LastKnownParent                      :
lastLogoff                           : 0
lastLogon                            : 0
LastLogonDate                        :
LockedOut                            : False
logonCount                           : 0
LogonWorkstations                    :
Manager                              :
MemberOf                             : {}
MNSLogonAccount                      : False
MobilePhone                          :
Modified                             : 18/11/2016 11:03:02
modifyTimeStamp                      : 18/11/2016 11:03:02
msDS-User-Account-Control-Computed   : 8388608
Name                                 : FOX Fred
nTSecurityDescriptor                 : System.DirectoryServices.ActiveDirectorySecurity
ObjectCategory                       : CN=Person,CN=Schema,CN=Configuration,DC=Manticore,DC=org
ObjectClass                          : user
ObjectGUID                           : db5a3975-980d-4749-b9c0-48aff9217b2a
objectSid                            : S-1-5-21-759617655-3516038109-1479587680-1314
Office                               :
OfficePhone                          :
Organization                         :
OtherName                            :
PasswordExpired                      : True
PasswordLastSet                      : 17/11/2016 14:07:13
PasswordNeverExpires                 : False
PasswordNotRequired                  : False
POBox                                :
PostalCode                           :
PrimaryGroup                         : CN=Domain Users,CN=Users,DC=Manticore,DC=org
primaryGroupID                       : 513
PrincipalsAllowedToDelegateToAccount : {}
ProfilePath                          :
ProtectedFromAccidentalDeletion      : False
pwdLastSet                           : 131238652330182673
SamAccountName                       : foxfred
sAMAccountType                       : 805306368
ScriptPath                           :
sDRightsEffective                    : 15
ServicePrincipalNames                : {}
SID                                  : S-1-5-21-759617655-3516038109-1479587680-1314
SIDHistory                           : {}
SmartcardLogonRequired               : False
State                                :
StreetAddress                        :
Surname                              :
Title                                :
TrustedForDelegation                 : False
TrustedToAuthForDelegation           : False
UseDESKeyOnly                        : False
userAccountControl                   : 512
userCertificate                      : {}
UserPrincipalName                    :
uSNChanged                           : 78123
uSNCreated                           : 62259
whenChanged                          : 18/11/2016 11:03:02
whenCreated                          : 17/11/2016 14:07:13


Using –properties * returns ALL of the properties of a user. That’s OK if you’re looking at one, or a few users, but becomes a very expensive operation if you’re looking at thousands of user objects.


A more elegant approach is to specify the properties you want:

PS> Get-ADUser -Identity foxfred -Properties EmailAddress, LockedOut, ProtectedFromAccidentalDeletion, whenCreated
DistinguishedName               : CN=FOX Fred,OU=UserAccounts,DC=Manticore,DC=org
EmailAddress                    :
Enabled                         : True
GivenName                       :
LockedOut                       : False
Name                            : FOX Fred
ObjectClass                     : user
ObjectGUID                      : db5a3975-980d-4749-b9c0-48aff9217b2a
ProtectedFromAccidentalDeletion : False
SamAccountName                  : foxfred
SID                             : S-1-5-21-759617655-3516038109-1479587680-1314
Surname                         :
UserPrincipalName               :
whenCreated                     : 17/11/2016 14:07:13


You get the properties you specified and the default properties.


So, while Get-ADUser doesn’t display all properties you can overcome this by using the –properties parameter with a * for all properties or a list of the properties you want in addition to the defaults.

Active Directory Schema Versions

With the release of Windows Server 2016 its time to update my schema versions script


$sch = [System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema]::GetCurrentSchema()
$de = $sch.GetDirectoryEntry()
switch ($de.ObjectVersion) {
13{"{0,25} " -f "Schema Version $($de.ObjectVersion) = Windows 2000"; break}
30{"{0,25} " -f "Schema Version $($de.ObjectVersion) = Windows 2003"; break}
31{"{0,25} " -f "Schema Version $($de.ObjectVersion) = Windows 2003 R2"; break}
44{"{0,25} " -f "Schema Version $($de.ObjectVersion) = Windows 2008"; break}
47{"{0,25} " -f "Schema Version $($de.ObjectVersion) = Windows 2008 R2"; break}
56{"{0,25} " -f "Schema Version $($de.ObjectVersion) = Windows 2012"; break}
69{"{0,25} " -f "Schema Version $($de.ObjectVersion) = Windows 2012 R2"; break}
87{"{0,25} " -f "Schema Version $($de.ObjectVersion) = Windows 2016"; break}
default{"{0,25} {1,2} " -f "Unknown Schema Version", $($de.ObjectVersion); break}


The script uses the GetCurrentSchema static method on System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema. Derives a directory entry and uses the ObjectVersion to determine the corresponding Windows Server version.

Changing the samAccountName

I was recently asked how the samAccountName – also referred to as the login id – could be changed.


First lets look at an account:

PS C:\Scripts> Get-ADUser -Identity 'FredFox'
DistinguishedName : CN=FOX Fred,OU=UserAccounts,DC=Manticore,DC=org
Enabled           : True
GivenName         :
Name              : FOX Fred
ObjectClass       : user
ObjectGUID        : db5a3975-980d-4749-b9c0-48aff9217b2a
SamAccountName    : FredFox
SID               : S-1-5-21-759617655-3516038109-1479587680-1314
Surname           :
UserPrincipalName :

Once you’ve confirmed you have the correct account then pipe it into Set-ADUser and use the –samAccountName parameter:

PS C:\Scripts> Get-ADUser -Identity 'FredFox' | Set-ADUser -SamAccountName 'foxfred' -PassThru
DistinguishedName : CN=FOX Fred,OU=UserAccounts,DC=Manticore,DC=org
Enabled           : True
GivenName         :
Name              : FOX Fred
ObjectClass       : user
ObjectGUID        : db5a3975-980d-4749-b9c0-48aff9217b2a
SamAccountName    : foxfred
SID               : S-1-5-21-759617655-3516038109-1479587680-1314
Surname           :
UserPrincipalName :


I used the –Passthru parameter so the new account details are shown. Note that the User Principal Name (UPN) isn’t changed. Use the –UserPrincipalName parameter as well if you need to change the UPN at the same time

Creating test accounts in Active Directory

There’s often a need to create test accounts in AD. You may want to create a a set of test accounts or if you have a demo/test lab you may need accounts in that.

Creating the names for the accounts is a pain unless you go down the test1, test2 etc route.

One way to real looking names is iuse a couple of loops like this

$fnames = @(

$lnames = @(

$secpass = Read-Host -Prompt 'Password' -AsSecureString
$ou = "OU=UserAccounts,DC=Manticore,DC=org"

foreach ($fname in $fnames){
foreach ($lname in $lnames){
$name = $lname.TOUpper() + " $fname"
$sam = "$fname$lname"
$upn = "$"

New-ADUser -Name $name -SamAccountName $sam -UserPrincipalName $upn -AccountPassword $secpass -Path $ou -Enabled $true


First create an array of first names & another array of second names


Get a secure string for the Password – I’m using the same password for all as its my demo/test environment


Set the OU you want the accounts in.


Iterate over the set of first names and in that loop iterate over the last name. Within the inner loop create the name, samAccountName and UPN and call New-ADUser.


You end up with a set of new accounts where every first name is joined with every last name to create accounts. Names look a bit samey but for demo environment it works. Also, saves you having to think up individual names.


I’ve used   10 names in each of the first and last name arrays so end up with 100 new accounts.

Creating a new AD forest

As I’ve completely rebuilt my demo/lab machine I need to re-create the Active Directory

This is now so simple even on a server core machine


First install the roles and features needed

Add-WindowsFeature -Name AD-Domain-Services, RSAT-AD-PowerShell, DNS, RSAT-DNS-Server, DHCP, RSAT-DHCP


This adds AD, DNS, DHCP and the appropriate admin tools – as its server core we’re really talking about the relevant PowerShell modules

Installing AD just gets you ready – it doesn’t create the forest


You get the ADDSDeployment module

PS C:\Scripts> Get-Command -Module ADDSDeployment



To create the forest and the first domain controller

PS C:\Scripts> Install-ADDSForest -DomainName '' -ForestMode Default -DomainMode Default -InstallDns
SafeModeAdministratorPassword: ********


You’ll be asked to confirm the safe mode password


Default for forest and domain mode matches the Windows version


PS C:\Users\Administrator> Get-ADForest

ApplicationPartitions : {}
CrossForestReferences : {}
DomainNamingMaster    :
Domains               : {}
ForestMode            : Windows2016Forest
GlobalCatalogs        : {}
Name                  :
PartitionsContainer   : CN=Partitions,CN=Configuration,DC=Manticore,DC=org
RootDomain            :
SchemaMaster          :
Sites                 : {Default-First-Site-Name}
SPNSuffixes           : {}
UPNSuffixes           : {}


PS C:\Users\Administrator> Get-ADDomain

AllowedDNSSuffixes                 : {}
ChildDomains                       : {}
ComputersContainer                 : CN=Computers,DC=Manticore,DC=org
DeletedObjectsContainer            : CN=Deleted Objects,DC=Manticore,DC=org
DistinguishedName                  : DC=Manticore,DC=org
DNSRoot                            :
DomainControllersContainer         : OU=Domain Controllers,DC=Manticore,DC=org
DomainMode                         : Windows2016Domain
DomainSID                          : S-1-5-21-759617655-3516038109-1479587680
ForeignSecurityPrincipalsContainer : CN=ForeignSecurityPrincipals,DC=Manticore,DC=org
Forest                             :
InfrastructureMaster               :
LastLogonReplicationInterval       :
LinkedGroupPolicyObjects           : {CN={31B2F340-016D-11D2-945F-00C04FB984F9},CN=Policies,CN=System,DC=Manticore,DC=o
LostAndFoundContainer              : CN=LostAndFound,DC=Manticore,DC=org
ManagedBy                          :
Name                               : Manticore
NetBIOSName                        : MANTICORE
ObjectClass                        : domainDNS
ObjectGUID                         : 05d9aa61-d422-4728-9595-77754934b948
ParentDomain                       :
PDCEmulator                        :
PublicKeyRequiredPasswordRolling   : True
QuotasContainer                    : CN=NTDS Quotas,DC=Manticore,DC=org
ReadOnlyReplicaDirectoryServers    : {}
ReplicaDirectoryServers            : {}
RIDMaster                          :
SubordinateReferences              : {CN=Configuration,DC=Manticore,DC=org}
SystemsContainer                   : CN=System,DC=Manticore,DC=org
UsersContainer                     : CN=Users,DC=Manticore,DC=org