Skip to main content

Tutorial: Using Active Directory from Linux with PowerShell (pwsh)

0) Prerequisites

  • A Windows Server or management VM joined to the AD domain with RSAT installed (ActiveDirectory PowerShell module).
  • WinRM enabled on that Windows host.
  • On Linux: pwsh installed. Optional but recommended: Kerberos configured (/etc/krb5.conf) so you can use Kerberos (Negotiate) instead of Basic.

1) Prepare the Windows AD Management Host (one-time)

Run these on a Windows Server or a Windows 10/11 admin VM that is domain-joined.

# Install RSAT Active Directory tools (if not already present)
# Windows Server:
Install-WindowsFeature RSAT-AD-PowerShell

# Windows 10/11 (admin PowerShell):
Add-WindowsCapability -Online -Name Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0

# Enable PowerShell remoting (WinRM)
Enable-PSRemoting -Force

# Optional: allow remoting only from specific subnets or via HTTPS, per your security policy
# For domain environments using Kerberos, HTTP with mutual auth is common.

Confirm the AD module is available:

Get-Module -ListAvailable ActiveDirectory

2) From Linux: Connect to the AD Host using pwsh

If you have Kerberos configured on Linux, obtain a ticket (optional but recommended):

Open pwsh on Linux, then create a session to the Windows AD host:

# Option A: Kerberos/Negotiate (preferred in-domain)
$cred = Get-Credential # enter DOMAIN\user or user@domain and password
$s = New-PSSession -ComputerName dc1.example.com -Authentication Negotiate -Credential $cred

# Option B: Basic over HTTPS (only if your WinRM listener is HTTPS)
# $s = New-PSSession -ComputerName dc1.example.com -UseSSL -Authentication Basic -Credential $cred

# Enter interactive session (optional)
Enter-PSSession $s

Inside the remote session (or via Invoke-Command), load the AD module and test:

Import-Module ActiveDirectory
Get-ADDomain
Get-ADForest

If you prefer to run AD cmdlets locally without staying in a remote session, use implicit remoting:

# From your local pwsh session on Linux
Import-Module ActiveDirectory -PSSession $s
# Now AD cmdlets are proxied to the Windows host:
Get-ADUser -Filter 'Name -like "*"' -ResultSetSize 5

3) Common Day-to-Day AD Tasks (via AD module)

3.1 Query users, groups, computers

# Users
Get-ADUser -Identity jdoe -Properties mail,department,title
Get-ADUser -Filter 'Enabled -eq $true' -SearchBase "OU=Users,OU=Corp,DC=example,DC=com" -ResultSetSize 50

# Groups
Get-ADGroup -Identity "Sec-IT-Admins" -Properties ManagedBy
Get-ADGroupMember -Identity "Sec-IT-Admins"

# Computers
Get-ADComputer -Filter 'OperatingSystem -like "*Windows*"' -Properties OperatingSystem

3.2 Create and update users

# Create a user
New-ADUser -Name "Jane Doe" -SamAccountName jdoe `
-GivenName Jane -Surname Doe `
-UserPrincipalName [email protected] `
-Path "OU=Users,OU=Corp,DC=example,DC=com" `
-Enabled $true

# Set password and force change at next logon
Set-ADAccountPassword -Identity jdoe -Reset -NewPassword (ConvertTo-SecureString 'Str0ngP@ss!' -AsPlainText -Force)
Set-ADUser -Identity jdoe -ChangePasswordAtLogon $true

3.3 Unlock or disable accounts

Unlock-ADAccount -Identity jdoe
Disable-ADAccount -Identity temp.user
Enable-ADAccount -Identity temp.user

3.4 Group membership

Add-ADGroupMember -Identity "Sec-IT-Admins" -Members jdoe
Remove-ADGroupMember -Identity "Sec-IT-Admins" -Members jdoe -Confirm:$false

3.5 Move objects and manage OUs

# Move a user to a different OU
Get-ADUser jdoe | Move-ADObject -TargetPath "OU=Engineering,OU=Corp,DC=example,DC=com"

# Create an OU
New-ADOrganizationalUnit -Name "Lab" -Path "OU=Corp,DC=example,DC=com"

4) Bulk Operations from CSV

Prepare a CSV (example: /tmp/new_users.csv):

GivenName,Surname,SamAccountName,UPN,OU,TempPassword
Alice,Ng,ang,[email protected],"OU=Users,OU=Corp,DC=example,DC=com",S0meP@ss!
Bob,Lee,blee,[email protected],"OU=Users,OU=Corp,DC=example,DC=com",S0meP@ss!

Import and create:

$rows = Import-Csv /tmp/new_users.csv
foreach ($r in $rows) {
New-ADUser -Name "$($r.GivenName) $($r.Surname)" `
-GivenName $r.GivenName -Surname $r.Surname `
-SamAccountName $r.SamAccountName `
-UserPrincipalName $r.UPN `
-Path $r.OU -Enabled $true

Set-ADAccountPassword -Identity $r.SamAccountName -Reset -NewPassword (ConvertTo-SecureString $r.TempPassword -AsPlainText -Force)
Set-ADUser -Identity $r.SamAccountName -ChangePasswordAtLogon $true
}

5) Running AD Commands Without a Windows Hop (LDAP from pwsh)

This approach uses LDAP/LDAPS directly from Linux. It does not require RSAT, but your Domain Controllers must allow LDAP or LDAPS (636 recommended with a proper certificate). This is useful for read operations or specific write operations when you cannot use WinRM.

# Minimal LDAP search using .NET's DirectoryServices.Protocols
$domain = "example.com"
$dc = "dc1.example.com" # or a VIP/load balancer for LDAP
$bindUser = "EXAMPLE\ldap_svc" # or [email protected]
$bindPass = "YourPasswordHere"
$baseDN = "DC=example,DC=com"

Add-Type -AssemblyName System.DirectoryServices.Protocols

$cred = New-Object System.Net.NetworkCredential($bindUser, $bindPass)
$conn = New-Object System.DirectoryServices.Protocols.LdapConnection($dc)
$conn.Credential = $cred
$conn.AuthType = [System.DirectoryServices.Protocols.AuthType]::Negotiate
$conn.SessionOptions.ProtocolVersion = 3

# For LDAPS:
# $conn.SessionOptions.SecureSocketLayer = $true
# $conn.SessionOptions.VerifyServerCertificate = { param($c,$cert) $true } # replace with proper validation

$conn.Bind()

# Search for enabled users in a specific OU
$searchBase = "OU=Users,OU=Corp,$baseDN"
$filter = "(&(objectClass=user)(!(objectClass=computer))(userAccountControl:1.2.840.113556.1.4.803:=512))"
$attrs = @("cn","sAMAccountName","mail")

$req = New-Object System.DirectoryServices.Protocols.SearchRequest($searchBase, $filter, "Subtree", $attrs)
$res = $conn.SendRequest($req)

foreach ($entry in $res.Entries) {
$cn = $entry.Attributes["cn"][0]
$sam = $entry.Attributes["sAMAccountName"][0]
$mail= $entry.Attributes["mail"][0]
"$cn`t$sam`t$mail"
}

$conn.Dispose()

Modify operations (example: add a user to a group by updating the group’s member attribute):

$groupDN = "CN=Sec-IT-Admins,OU=Groups,OU=Corp,DC=example,DC=com"
$userDN = "CN=Jane Doe,OU=Users,OU=Corp,DC=example,DC=com"

$mod = New-Object System.DirectoryServices.Protocols.DirectoryAttributeModification
$mod.Name = "member"
$mod.Operation = [System.DirectoryServices.Protocols.DirectoryAttributeOperation]::Add
$mod.Add($userDN)

$mreq = New-Object System.DirectoryServices.Protocols.ModifyRequest($groupDN, $mod)
$conn.SendRequest($mreq)

6) Troubleshooting

WinRM or remoting errors

# From the Windows host
winrm quickconfig
winrm enumerate winrm/config/listener
Test-WSMan
  • Ensure DNS resolves the Windows host from Linux and vice versa.
  • Verify that the Windows host’s firewall allows WinRM (HTTP 5985, HTTPS 5986).
  • If not using Kerberos, prefer HTTPS and Basic only when strictly required and permitted.

Kerberos issues

  • Ensure Linux clock skew is under 5 minutes compared to the domain controllers.
  • Verify /etc/krb5.conf realm and KDC entries are correct.
  • Use klist to confirm you have a valid TGT.

LDAP/LDAPS issues

  • Test connectivity: telnet dc1.example.com 389 or openssl s_client -connect dc1.example.com:636.
  • For LDAPS, ensure the DC has a valid server certificate trusted by your Linux machine.
  • Check base DN and filters for correctness.

7) Quick Reference

# Start a remote session to AD host and load the module
$cred = Get-Credential
$s = New-PSSession -ComputerName dc1.example.com -Authentication Negotiate -Credential $cred
Import-Module ActiveDirectory -PSSession $s

# Examples
Get-ADUser -Identity jdoe -Properties *
New-ADUser -Name "Jane Doe" -SamAccountName jdoe -UserPrincipalName [email protected] -Path "OU=Users,OU=Corp,DC=example,DC=com" -Enabled $true
Set-ADAccountPassword -Identity jdoe -Reset -NewPassword (ConvertTo-SecureString 'Str0ngP@ss!' -AsPlainText -Force)
Add-ADGroupMember -Identity "Sec-IT-Admins" -Members jdoe
Get-ADComputer -Filter * -SearchBase "OU=Servers,DC=example,DC=com"