Monthly Archive


PowerShell and Active Directory

Get-AdUser in PowerShell Core

There has been a problem with Get-ADUser in PowerShell core such that

Get-ADUser -Identity Richard -Properties *


Throws an error.


The problem is in .NET Core and affects a small number of properties including ProtectedFromAccidentalDeletion


The underlying .NET Core issue has been fixed and PowerShell v7 preview 3 on Windows 10.0.18362 will successfully run the command

Putting user information into computer description

I was recently asked how to add user information – specifically first and last name – into computer description in Active Directory.

First get your user

$user = Get-ADUser -Identity FredBrown


Then add the information to the computer’s description

Set-ADComputer -Identity W10ProIp -Description "Used by $($user.Givenname) $($user.Surname)"


You need to use the subexpression – $() – syntax to resolve the values rather than getting references to the $user object.


Then test the description has been set

Get-ADComputer -Identity W10ProIp -Properties Description


If it was me I’d add the samAccountName or other unique identifier as name isn’t sufficient to uniquely identify the user

Moving FSMO roles in PowerShell v6.1.1

With the Windows Server 2019 media now being available again it’s time to move my test lab over to the new version. I’d built a Windows Server 2019 VM and installed PowerShell v6.1.1. I discovered that in Server 2019 and the Windows 10 October 2018 update that the AD module worked in PowerShell v6.1.1. I decided to try moving FSMO roles in PowerShell v6.1.1 as I updated the domain and removed the old Server 2016 domain controller.


The usual schema update went smoothly – updated the schema version to 88 from 87. Installing AD domain services and DNS on the new DC worked. Promoting the Windows 2019 system to be a DC worked with no problems.


Time to move the FSMO roles. They would move automatically when the old DC was removed but its always better to control the action.

Import-Module ActiveDirectory

will load the AD module into PowerShell v6.1.1.


There are 5 FSMO roles – 2 at the forest level

PS> Get-ADForest | Format-List Name, *master

Name :
DomainNamingMaster :
SchemaMaster :


And 3 at the domain level – I only have a single domain to worry about.

PS> Get-ADDomain | Format-List *master, PDC*

InfrastructureMaster :
RIDMaster :
PDCEmulator :


The forest level FSMO roles moved:

PS> Move-ADDirectoryServerOperationMasterRole -Identity W19DC01 -OperationMasterRole DomainNamingMaster -Confirm:$false

PS> Move-ADDirectoryServerOperationMasterRole -Identity W19DC01 -OperationMasterRole SchemaMaster -Confirm:$false

PS> Get-ADForest | Format-List Name, *master

Name :
DomainNamingMaster :
SchemaMaster :


For the domain level FSMO roles I decided to get ambitious

PS> Move-ADDirectoryServerOperationMasterRole -Identity W19DC01 -OperationMasterRole RIDMaster, InfrastructureMaster, PDCEmulator -Confirm:$false

PS> Get-ADDomain | Format-List *master, PDC*

InfrastructureMaster :
RIDMaster :
PDCEmulator :


Moving FSMO roles in PowerShell v6.1.1 was successful

Active Directory cmdlets in PowerShell v6.1.1

Just discovered that you can run the Active Directory cmdlets in PowerShell v6.1.1 BUT there is a huge caveat.


The Windows 10 October 2018 (Windows 10 1809) update includes the RSAT tools (including the AD tools) as optional features. This means that you can easily install the AD tools:

Add-WindowsCapability -Name Rsat.ActiveDirectory.DS-LDS.Tools~~~~ –Online


The AD PowerShell module now has a version with the PSEdition set as Core,Desk


I’m running the Windows 10 Insider Preview build 18282 which has moved on a bit from Windows 10 1809 and PowerShell Core v6.1.1.


The AD module can be imported and the commands that I’ve tried have worked.


For the AD cmdlets to work in PowerShell Core looks like you need Windows 10 October 2018 update or later and PowerShell v6.1.1 or later. I’m going to say you should upgrade to v6.1.1 if you have v6.1 as the later version fixes a .NET security bug.


This is a big step forward for PowerShell Core being an acceptable replacement for Windows PowerShell though there are still a few gaps to be filled.

Windows Compatibility Pack

As reported last month the Windows Compatibility Pack for .NET core is available. This adds back some of the functionality missing from .NET core. This functionality is ONLY of relevance on Windows machines.


A PowerShell module based on the Compatibility Pack is in the works – this will add a number of cmdlets including the WMI cmdlets back into PowerShell v6 on Windows. There’s no ETA on the module at this time.


There is a module on the PowerShell gallery that will add the .NET components of the Compatibility Pack into your PowerShell v6 session.

PS>  Find-Module -Name PSCoreWindowsCompat | ft -a

Version Name                Repository Description 
 ------- ----                ---------- ----------- PSCoreWindowsCompat PSGallery  Provides the Microsoft.Windows.Compatibility Pack to PowerShell Core.


If you want to inspect the module

PS>  Save-Module -Name PSCoreWindowsCompat -Repository PSGallery -Path C:\Source\ -Force


To install the module:

PS>  Install-Module -Name PSCoreWindowsCompat -Repository PSGallery -Verbose -Force 
 VERBOSE: Repository details, Name = 'PSGallery', Location = ''; IsTrusted = 'False'; IsRegistered = 'True'. 
 VERBOSE: Using the provider 'PowerShellGet' for searching packages. 
 VERBOSE: Using the specified source names : 'PSGallery'. 
 VERBOSE: Getting the provider object for the PackageManagement Provider 'NuGet'. 
 VERBOSE: The specified Location is '' and PackageManagementProvider is 'NuGet'. 
 VERBOSE: Searching repository ''PSCoreWindowsCompat'' for ''. 
 VERBOSE: Total package yield:'1' for the specified package 'PSCoreWindowsCompat'. 
 VERBOSE: Performing the operation "Install-Module" on target "Version '' of module 'PSCoreWindowsCompat'". 
 VERBOSE: The installation scope is specified to be 'AllUsers'. 
 VERBOSE: The specified module will be installed in 'C:\Program Files\PowerShell\Modules'. 
 VERBOSE: The specified Location is 'NuGet' and PackageManagementProvider is 'NuGet'. 
 VERBOSE: Downloading module 'PSCoreWindowsCompat' with version '' from the repository ''. 
 VERBOSE: Searching repository ''PSCoreWindowsCompat'' for ''. 
 VERBOSE: InstallPackage' - name='PSCoreWindowsCompat', version='',destination='C:\Users\Richard.MANTICORE\AppData\Local\Temp\51711061' 
 VERBOSE: DownloadPackage' - name='PSCoreWindowsCompat', version='',destination='C:\Users\Richard.MANTICORE\AppData\Local\Temp\51711061\PSCoreWindowsCompat\PSCoreWindowsCompat.nupkg', uri='' 
 VERBOSE: Downloading ''. 
 VERBOSE: Completed downloading ''. 
 VERBOSE: Completed downloading 'PSCoreWindowsCompat'. 
 VERBOSE: InstallPackageLocal' - name='PSCoreWindowsCompat', version='',destination='C:\Users\Richard.MANTICORE\AppData\Local\Temp\51711061' 
 VERBOSE: Catalog file '' is not found in the contents of the module 'PSCoreWindowsCompat' being installed. 
VERBOSE: Module 'PSCoreWindowsCompat' was installed successfully to path 'C:\Program Files\PowerShell\Modules\PSCoreWindowsCompat\'.


Notice the installation path is OUTSIDE of the current version of PowerShell v6 so should remain available through any upgrades.

PS>  Import-Module -Name PSCoreWindowsCompat


Now you’ve got it how do you use it? The module DOESN’T have any functions – it just loads the .NET namespaces.


Its back to PowerShell v1 days – everything is a script instead of a cmdlet. For instance the Compatibility pack contains the System.DirectoryServices so you can script against AD.


Let’s say you want to see all the users in the domain:

$dom = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() 
 $root = $dom.GetDirectoryEntry()

$search = [System.DirectoryServices.DirectorySearcher]$root 
 $search.Filter = "(&(objectclass=user)(objectcategory=user))" 
 $search.SizeLimit = 3000 
 $result = $search.FindAll()

foreach ($user in $result){ 


Use System.DirectoryServices.ActiveDirectory.Domain to get the current domain. Create a System.DirectoryServices.DirectorySearcher object and set the filter to users. Find all of the users and display their distinguishedname


Its a lot more complicated than using the cmdlet:

PS>  Get-ADUser -Filter * | select DistinguishedName


but it gets the job done.


If you need to administer AD and you need the cross platform capabilities of PowerShell maybe you should use the  PSCoreWindowsCompat module (with aplogies to A-Team fans everywhere)

Get an AD user’s manager

Interesting question on the forum about finding the manager for a given user in AD – assuming the Manager field is populated of course. If you’ve not worked with the AD cmdlets this is a good introduction to some of their quirks. This is how you get an AD user’s manager.


You need the manager property on the AD user account but that’s not one of the default properties that’s returned so you need to use the –Propertie parameter to ensure you get your data. Assuming you have a csv file with userids that looks like this

id userid 
-- ------ 
1  DonBrown 
2  DonFox 
3  JamesBrown 
4  JamesBlack


You can use this code

Import-Csv -Path .\names.csv | 
foreach { 
   $user = Get-ADUser -Identity $_.userid -Properties Manager 
   $_ | Add-Member -MemberType NoteProperty -Name 'Manager' -Value $user.Manager 


This returns the distinguished name of the manager

id userid     Manager                                           
-- ------     -------                                           
1  DonBrown   CN=HARRIS Fred,OU=UserAccounts,DC=Manticore,DC=org 
2  DonFox     CN=HARRIS Fred,OU=UserAccounts,DC=Manticore,DC=org 
3  JamesBrown CN=HARRIS Fred,OU=UserAccounts,DC=Manticore,DC=org 
4  JamesBlack CN=HARRIS Fred,OU=UserAccounts,DC=Manticore,DC=org


if you prefer to have their name then you need an extra step

Import-Csv -Path .\names.csv | 
foreach { 
   $user = Get-ADUser -Identity $_.userid -Properties Manager 
   $manager = Get-ADUser -Identity $user.Manager 
   $_ | Add-Member -MemberType NoteProperty -Name 'Manager' -Value $manager.Name 


Which gives output like this

id userid     Manager    
-- ------     -------    
1  DonBrown   HARRIS Fred 
2  DonFox     HARRIS Fred 
3  JamesBrown HARRIS Fred 
4  JamesBlack HARRIS Fred


If you need to output the data to a csv file then just add Export-Csv

Import-Csv -Path .\names.csv | 
foreach { 
   $user = Get-ADUser -Identity $_.userid -Properties Manager 
   $manager = Get-ADUser -Identity $user.Manager 
   $_ | Add-Member -MemberType NoteProperty -Name 'Manager' -Value $manager.Name 
 } | Export-Csv -Path names2.csv –NoTypeInformation

Comparing AD group membership on EmployeeId

Back in this post - I showed how to compare the membership of two groups using Compare-Object. The comparison was based on the samAccountName. A question raised the issue of comparing AD group membership on EmployeeId

In the case in particular users have multiple accounts BUT the EmployeeId is correct on all and will therefore show matching users. Assuming the EmployeeId is correct on all accounts it still leaves a problem.

When you run Get-ADGroupMember you get a very limited number of properties returned:

PS>  Get-ADGroupMember -Identity Testgroup1

distinguishedName : CN=JONES James,OU=UserAccounts,DC=Manticore,DC=org 
 name              : JONES James 
objectClass       : user 
objectGUID        : 027cb406-a3b0-4f45-9bbd-db47ccfb9212 
 SamAccountName    : JamesJones 
 SID               : S-1-5-21-759617655-3516038109-1479587680-1225

First thing I needed to do was set up some users with an EmployeeId

$ei = 1 
 Get-ADUser -Filter {Name -like "*Jones*"} -Properties EmployeeId | 
foreach { 
  $id =  23945 + $ei 
  $psitem | Set-ADUser -EmployeeID $id

  $ei = $ei + (Get-Random -Minimum 3 -Maximum 12) 

Get a set of users – including the EmployeeId – and forech of them set the id. The id is randomly generated based on a starting value and increment.

Now that the users have an Employeeid you can use that for comparison purposes

$group1 = Get-ADGroupMember -Identity Testgroup1 | 
foreach { 
  Get-ADUser -Identity $psitem.distinguishedName -Properties EmployeeId | 
  select -ExpandProperty EmployeeId 

$group2 = Get-ADGroupMember -Identity Testgroup2 | 
foreach { 
  Get-ADUser -Identity $psitem.distinguishedName -Properties EmployeeId | 
  select -ExpandProperty EmployeeId 

 Compare-Object -ReferenceObject $group1 -DifferenceObject $group2 -IncludeEqual |             
 where SideIndicator -eq "==" |            
foreach {            
  $id = ($_.InputObject)        
  Get-ADUser -Filter {EmployeeId -eq $id} -Properties EmployeeId            

Get the membership of the first group and for each member use Get-ADUser to return the EmployeeId. Repeat for the second group.

Use  Compare-Object to compare the two sets of group members – you’re looking for matches indicated by “==”

Foreach match get the AD user account filtering on the EmployeeID.

The PROBLEM with this approach is that you’ll get all user accounts returned that have the particular EmployeeId.   You can replace the line

Get-ADUser -Filter {EmployeeId -eq $id} -Properties EmployeeId


Get-ADUser -Filter {EmployeeId -eq $id} -Properties EmployeeId, MemberOf | where {$_.MemberOf -like "*Testgroup1*" -AND $_.MemberOf -like  "*Testgroup2*"}

Which should resolve the problem

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.