Skip to content

Instantly share code, notes, and snippets.

@PanosGreg
Last active April 24, 2024 08:37
Show Gist options
  • Save PanosGreg/dd0144d8496ebe850c38bb609bd32c91 to your computer and use it in GitHub Desktop.
Save PanosGreg/dd0144d8496ebe850c38bb609bd32c91 to your computer and use it in GitHub Desktop.
Function that finds AD Principals, namely Users, Computers or Groups
function Get-ADPrincipal {
<#
.SYNOPSIS
Get users,computers or groups from Active Directory, without the need of the ActiveDirectory module.
The examples show the use of the .Members property, the .GetGroups() method and also
the .Add() and .Remove() methods from the group members list along with the .Save() method.
.EXAMPLE
Get-ADPrincipal *test*
Get all the users with a given name.
The name parameter can accept wildcards.
.EXAMPLE
(Get-AdPrincipal -SamAccountName john.doe).GetGroups() | select Name,SamAccountName
Show all the groups that a user is a member of.
.EXAMPLE
$users = (Get-ADPrincipal 'Domain Admins' -Type Group).Members
$users | sort enabled,name | select Name,SamAccountName,Enabled
Get all the users that are part of a given group, in this case the Domain Admins group.
.EXAMPLE
$grp = Get-ADPrincipal app-admin* -Type Group
$usr = Get-ADPrincipal -SamAccountName john.doe
if (-not $grp.Members.Contains($usr)) {$grp.Members.Add($usr)}
elseif ($grp.Members.Contains($usr)) {[void]$grp.Members.Remove($usr)}
$grp.Save()
Add/Remove users to/from groups.
You'll need to have the appropriate AD permissions to add/remove users into/from a group
.EXAMPLE
Get-ADPrincipal -Type Computer -Name lab-gmsa
When you want to get a gMSA (Group Managed Service Account),
then you need to use the Computer type.
.NOTES
About the CleanUp switch, and why I don't dispose/clean-up the objects by default.
If we dispose the objects, then the end-user cannot use properties or methods
like .Members or .GetMembers() on a group to get back the members, or .GetGroups()
on a user to get back the groups he belongs to, or .Add() / .Remove() on the Members of a group
Simply because the objects would've been disposed and you'll get an error like this:
Exception calling "GetMembers" with "0" argument(s): "Cannot access a disposed object."
This error is an example when trying to use the .GetMembers() method on a group.
Alternatively on the same example if you try to access the .Members property
you won't get an error but you'll get back nothing.
Another note is about gMSA type of accounts. Now these are not technically Users.
But still from a principal perspective, you can get them with this function using the Computer type.
#>
[CmdletBinding(DefaultParameterSetName = 'Name')]
[OutputType('System.DirectoryServices.AccountManagement.UserPrincipal')] # <-- when Type is User
[OutputType('System.DirectoryServices.AccountManagement.GroupPrincipal')] # <-- when Type is Group
[OutputType('System.DirectoryServices.AccountManagement.ComputerPrincipal')] # <-- when Type is Computer
# Note: Have to use 'type' strings for the type names, cause if we use literal [types],
# then the function won't work if the assembly is not loaded into the session, cause PS won't know these types
param (
[Parameter(Mandatory,Position=0,ParameterSetName = 'Name')]
[string]$Name,
[Parameter(Mandatory,Position=0,ParameterSetName = 'SamAccountName')]
[string]$SamAccountName,
[ValidateSet('User','Group','Computer')] # <-- the Computer type can also be used for gMSA's
[string]$Type = 'User',
[string]$DomainName = $env:USERDNSDOMAIN,
[pscredential]$Credential, # <-- user/pass to connect to AD
[switch]$CleanUp
)
if ( -not ('System.DirectoryServices.AccountManagement.PrincipalContext' -as [type])) {
Add-Type -AssemblyName System.DirectoryServices.AccountManagement -ErrorAction Stop
}
. ([scriptblock]::Create('using namespace System.DirectoryServices.AccountManagement'))
try {
if ($PSBoundParameters.ContainsKey('Credential')) {
$UserName = $Credential.UserName
$Password = $Credential.GetNetworkCredential().Password
if ($UserName.IndexOf('\') -ge 1) {$UserName = $UserName.Split('\')[1]}
$Context = [PrincipalContext]::new([ContextType]::Domain,$DomainName,$UserName,$Password)
try {$IsValidUser = $Context.ValidateCredentials($UserName,$Password)}
catch {$IsValidUser = $false}
if (-not $IsValidUser) {throw "Invalid Username/Password or User $UserName is disabled"}
}
else {
$Context = [PrincipalContext]::new([ContextType]::Domain,$DomainName)
}
}
catch {throw $_}
if ([string]::IsNullOrWhiteSpace($Context.ConnectedServer)) {
$Context.Dispose()
throw "Could not connect to the domain $DomainName"
}
switch ($Type) {
'User' {$Principal = [UserPrincipal]::new($Context)}
'Group' {$Principal = [GroupPrincipal]::new($Context)}
'Computer' {$Principal = [ComputerPrincipal]::new($Context)}
}
try {
$SetName = $PSCmdlet.ParameterSetName
if ($SetName -eq 'Name') {$Principal.Name = $Name}
elseif ($SetName -eq 'SamAccountName') {$Principal.SamAccountName = $SamAccountName}
$Searcher = [PrincipalSearcher]::new()
$Searcher.QueryFilter = $Principal
$Searcher.FindAll()
}
catch {throw $_}
finally {
if ($CleanUp) {
# clean up
$Searcher.Dispose()
$Principal.Dispose()
$Context.Dispose()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment