I need to grab members in particular AD group and add them into array. Using net group I can easily get the members of AD group.
However, I am not familier with the filter on Windows. I just want to get the user name from output.
Group name test
Comment
Members
---------------------------------------------------------------------
mike tom jackie
rick jason nick
The command completed successfully.
I can't use Get-ADGroupMember command using PowerShell. If there is a way to get a data and filter using PowerShell, it is also OK.
Well, the good news is that there is rarely only one way to do things in PowerShell. Here's part of a larger script I have on hand for some group related things where I don't always have the AD module available (such as on servers that other teams own):
$Identity = 'test'
$LDAP = "dc="+$env:USERDNSDOMAIN.Replace('.',',dc=')
$Filter = "(&(sAMAccountName=$Identity)(objectClass=group))"
$Searcher = [adsisearcher]$Filter
$Searcher.SearchRoot = "LDAP://$LDAP"
'Member','Description','groupType' | %{$Searcher.PropertiesToLoad.Add($_)|Out-Null}
$Results=$Searcher.FindAll()
$GroupTypeDef = #{
1='System'
2='Global'
4='Domain Local'
8='Universal'
16='APP_BASIC'
32='APP_QUERY'
-2147483648='Security'
}
If($Results.Count -gt 0){
$Group = New-Object PSObject #{
'DistinguishedName'=[string]$Results.Properties.Item('adspath') -replace "LDAP\:\/\/"
'Scope'=$GroupTypeDef.Keys|?{$_ -band ($($Results.properties.item('GroupType')))}|%{$GroupTypeDef.get_item($_)}
'Description'=[string]$Results.Properties.Item('description')
'Members'=[string[]]$Results.Properties.Item('member')|% -Begin {$Searcher.PropertiesToLoad.Clear();$Searcher.PropertiesToLoad.Add('objectClass')|Out-Null} {$Searcher.Filter = "(distinguishedName=$_)";[PSCustomObject][ordered]#{'MemberType'=$Searcher.FindAll().Properties.Item('objectClass').ToUpper()[-1];'DistinguishedName'=$_}}
}
$Group|Select DistinguishedName,Scope,Description
$Group.Members|FT -AutoSize
}
Else{"Unable to find group '$Group' in '$env:USERDNSDOMAIN'.`nPlease check that you can access that domain from your current domain, and that the group exists."}
Here's one way to get the direct members of an AD group without using the AD cmdlets:
param(
[Parameter(Mandatory)]
$GroupName
)
$ADS_ESCAPEDMODE_ON = 2
$ADS_SETTYPE_DN = 4
$ADS_FORMAT_X500 = 5
function Invoke-Method {
param(
[__ComObject]
$object,
[String]
$method,
$parameters
)
$output = $object.GetType().InvokeMember($method,"InvokeMethod",$null,$object,$parameters)
if ( $output ) { $output }
}
function Set-Property {
param(
[__ComObject]
$object,
[String]
$property,
$parameters
)
[Void] $object.GetType().InvokeMember($property,"SetProperty",$null,$object,$parameters)
}
$Pathname = New-Object -ComObject "Pathname"
Set-Property $Pathname "EscapedMode" $ADS_ESCAPEDMODE_ON
$Searcher = [ADSISearcher] "(&(objectClass=group)(name=$GroupName))"
$Searcher.PropertiesToLoad.AddRange(#("distinguishedName"))
$SearchResult = $searcher.FindOne()
if ( $SearchResult ) {
$GroupDN = $searchResult.Properties["distinguishedname"][0]
Invoke-Method $Pathname "Set" #($GroupDN,$ADS_SETTYPE_DN)
$Path = Invoke-Method $Pathname "Retrieve" $ADS_FORMAT_X500
$Group = [ADSI] $path
foreach ( $MemberDN in $Group.member ) {
Invoke-Method $Pathname "Set" #($MemberDN,$ADS_SETTYPE_DN)
$Path = Invoke-Method $Pathname "Retrieve" $ADS_FORMAT_X500
$Member = [ADSI] $Path
"" | Select-Object `
#{
Name="group_name"
Expression={$Group.name[0]}
},
#{
Name="member_objectClass"
Expression={$member.ObjectClass[$Member.ObjectClass.Count - 1]}
},
#{
Name="member_sAMAccountName";
Expression={$Member.sAMAccountName[0]}
}
}
}
else {
throw "Group not found"
}
This version uses the Pathname COM object to handle name escaping and outputs the the object class and sAMAccountName for each member of the group.
Related
I am getting errors with Get-ADPrincipalGroupMembership command on Windows 10 (x64) machine. I have installed the required RSAT- 'Active directory Domain service and Lightweight Directory service tools' and 'Server manager' dependencies as specified int this document. I am able to execute Get-AdUser and see the results but Get-ADPrincipalGroupMembership is throwing below error.
PS C:\Users\JYOTHI> Get-ADPrincipalGroupMembership jyothi
Get-ADPrincipalGroupMembership : An unspecified error has occurred
At line:1 char:1
+ Get-ADPrincipalGroupMembership gapalani
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (jyothi:ADPrincipal) [Get-ADPrincipalGroupMembership], ADException
+ FullyQualifiedErrorId : ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADPrincipalGroupMembership
I can try the other way
(Get-Aduser jyothi -Properties MemberOf | Select MemberOf).MemberOf
but like to know what is the fix for Get-ADPrincipalGroupMembership
As you have noticed, Get-ADPrincipalGroupMembership fails with an obscure error if the reference object's name contains certain characters, or if it's a member of one or more groups that contain certain characters in their names.
I don't have definitive proof, but my testing indicates that the underlying issue is that Get-ADPrincipalGroupMembership, internally, uses ADSI and fails to correctly escape distinguished names that contain characters that need to be escaped. (If this is the case, Microsoft should be using the IADsPathname interface to escape names correctly. This would be an embarrassing oversight on their part.)
Unfortunately, this problem renders the cmdlet broken and unusable in production environments.
Here's a relatively short PowerShell script that doesn't suffer from this annoyance and also supports retrieving recursive group memberships:
# Get-ADGroupMembership.ps1
# Written by Bill Stewart
#requires -version 2
# Version history:
# 1.0 (2019-12-02)
# * Initial version. Only searches the current domain.
<#
.SYNOPSIS
Gets the distinguished names of the Active Directory groups that have a specified object as a member.
.DESCRIPTION
Gets the distinguished names of the Active Directory groups that have a specified object, represented by the -Identity parameter, as a member.
.PARAMETER Identity
Specifies an Active Directory object. You can specify either the distinguishedName or the sAMAccountName of the object.
.PARAMETER Recursive
Specifies to include the object's nested group memberships.
.NOTES
If you use the ActiveDirectory PowerShell module and want Microsoft.ActiveDirectory.Management.ADGroup objects as output, pipe this command's output to the Get-ADGroup cmdlet.
.EXAMPLE
Get the distinguished names of the groups that the kendyer account is a member of:
PS C:\> Get-ADGroupMembership kendyer
.EXAMPLE
Get the distinguished names of the groups that the kendyer account is a member of, including nested groups:
PS C:\> Get-ADGroupMembership kendyer -Recursive
.EXAMPLE
Get the ADGroup objects representing the groups that the kendyer account is a member of (requires the Active Directory module):
PS C:\> Get-ADGroupMembership kendyer | Get-ADGroup
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true,ValueFromPipeline = $true)]
[String[]] $Identity,
[Switch] $Recursive
)
begin {
$CommandName = $MyInvocation.MyCommand.Name
# Set up Pathname COM object
$ADS_ESCAPEDMODE_ON = 2
$ADS_SETTYPE_DN = 4
$ADS_FORMAT_X500_DN = 7
$Pathname = New-Object -ComObject "Pathname"
if ( -not $Pathname ) {
return
}
[Void] $Pathname.GetType().InvokeMember("EscapedMode","SetProperty",$null,$Pathname,$ADS_ESCAPEDMODE_ON)
# Outputs correctly escaped distinguished name using Pathname object
function Get-EscapedName {
param(
[String] $distinguishedName
)
[Void] $Pathname.GetType().InvokeMember("Set","InvokeMethod",$null,$Pathname,#($distinguishedName,$ADS_SETTYPE_DN))
$Pathname.GetType().InvokeMember("Retrieve","InvokeMethod",$null,$Pathname,$ADS_FORMAT_X500_DN)
}
# Outputs the memberOf attribute of an object using paged search (in case
# an object is a member of a large number of groups)
function Get-MemberOfAttribute {
param(
[String] $distinguishedName,
[Ref] $memberOf,
[Switch] $recursive
)
$searcher = [ADSISearcher] "(objectClass=*)"
$searcher.SearchRoot = [ADSI] "LDAP://$(Get-EscapedName $distinguishedName)"
$lastQuery = $false
$rangeStep = 1500
$rangeLow = 0
$rangeHigh = $rangeLow + ($rangeStep - 1)
do {
if ( -not $lastQuery ) {
$property = "memberOf;range={0}-{1}" -f $rangeLow,$rangeHigh
}
else {
$property = "memberOf;range={0}-*" -f $rangeLow
}
$searcher.PropertiesToLoad.Clear()
[Void] $searcher.PropertiesToLoad.Add($property)
$searchResults = $searcher.FindOne()
if ( $searchResults.Properties.Contains($property) ) {
foreach ( $searchResult in $searchResults.Properties[$property] ) {
if ( $memberOf.Value.Count -gt 100 ) {
Write-Progress `
-Activity $CommandName `
-Status "Getting membership of '$distinguishedName'" `
-CurrentOperation $searchResult
}
if ( $recursive ) {
if ( -not $memberOf.Value.Contains($searchResult) ) {
Get-MemberOfAttribute $searchResult $memberOf -recursive
}
}
if ( -not $memberOf.Value.Contains($searchResult) ) {
$memberOf.Value.Add($searchResult)
}
}
$done = $lastQuery
}
else {
if ( -not $lastQuery ) {
$lastQuery = $true
}
else {
$done = $true
}
}
if ( -not $lastQuery ) {
$rangeLow = $rangeHigh + 1
$rangeHigh = $rangeLow + ($rangeStep - 1)
}
}
until ( $done )
Write-Progress `
-Activity $CommandName `
-Status "Getting membership of '$distinguishedName'" `
-Completed:$true
}
function Get-ADGroupMembership {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[String] $identity,
[Switch] $recursive
)
$ldapString = $identity -replace '\\','\5c' -replace '\(','\28' -replace '\)','\29' -replace '\*','\2a' -replace '\/','\2f'
$searcher = [ADSISearcher] "(|(distinguishedName=$ldapString)(sAMAccountName=$ldapString))"
try {
$searchResults = $searcher.FindAll()
if ( $searchResults.Count -gt 0 ) {
foreach ( $searchResult in $searchResults ) {
$memberOf = New-Object Collections.Generic.List[String]
Get-MemberOfAttribute $searchResult.Properties["distinguishedname"][0] ([Ref] $memberOf) -recursive:$recursive
$memberOf
}
}
else {
Write-Error "Cannot find an object with identity '$identity'." -Category ObjectNotFound
}
}
catch {
Write-Error -ErrorRecord $_
}
finally {
$searchResults.Dispose()
}
}
}
process {
foreach ( $IdentityItem in $Identity ) {
Get-ADGroupMembership $IdentityItem -recursive:$Recursive
}
}
I've also added this script as a public gist on github in case something needs fixing or if I add new features.
Get-ADPrincipalGroupMembership -Identity "jyothi"
Hi apologies if this has been asked before. If so
I have function that builds a object array of group members. I can see it works fine inside the function but the return object is has exactly double the members - tried an ArrayList and that is even worse. Can somebody please explain what is going on....
function Get-MsolGroupMembers
{
[CmdletBinding()]
param
(
[Parameter(Mandatory=$true, Position=0)]
[string]
$SearchString
)
$groups = Get-MsolGroup -SearchString $SearchString -MaxResults 1
$retObjs = #()
Write-Host -fore Yellow $groups.Count 'Group(s) found'
foreach ($group in $groups)
{
$groupGUID = $group.ObjectId
$groupDisplayName = $group.DisplayName
$groupEmail = $group.EmailAddress
$groupType = $group.GroupType
$groupMembers = Get-MsolGroupMember -GroupObjectId $groupGUID -All
foreach ($groupMember in $groupMembers)
{
$Properties = #{"GroupDisplayName"=$groupDisplayName;
"GroupEmail"=$grouEmail;
"GroupType"=$groupType;
"MemberDisplayName"=$groupMember.DisplayName;
"MemberEmail"=$groupMember.EmailAddress;
"MemberType"=$groupMember.GroupMemberType}
$Obj = New-Object -TypeName PSObject -Property $Properties
Write-Output $Obj | select GroupDisplayName,GroupEmail,GroupType,MemberDisplayName,MemberEmail,MemberType
$retObjs += $Obj
}
return $reObjs;
}
}
$members = Get-MsolGroupMembers -SearchString 'My Test Group'
$members.Count
Sure, this is easy. You're outputting everything twice. Once with the Write-Output line, and then again with the return line. PowerShell functions return anything to the pipeline that is not specifically redirected (such as with Write-Host or Export-Csv), so both of those commands essentially do the same thing, which is where your doubling comes from. Remove one or the other and you'll be all set.
I found this function I'd like to use in a script I'm writing, but it keeps coming back $false when I can see an account is in a group and I can't figure out why?
function Check-IsGroupMember{
Param($user,$grp)
$strFilter = "(&(objectClass=Group)(name=" + $grp +"))"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.PageSize = 1000
$objSearcher.Filter = $strFilter
$objSearcher.SearchScope = "Subtree"
$colResults = $objSearcher.FindOne()
$objItem = $colResults.Properties
([string]$objItem.member).contains($user)
}
Usage:
Check-IsGroupMember "name of user" "DomainAdmins"
$objItem.member contains the DistinguishedName value of each principal who is a member of the group.
Even though the proper name of a person might be John Doe, the common name of the user account object may still be Doe, John, John G. Doe or anything else. This means that Contains() check (which is just a simple substring search) is not guaranteed to work as you expect.
The only real way to check is to either run another search for the user to find his/her DistinguishedName.
Personally, I would go for the AD PowerShell module from RSAT, rather than using a DirectorySearcher:
function Test-GroupMembership
{
Param(
[string]$UserName,
[string]$GroupName
)
$User = Get-ADUser -Identity $UserName
$Group = Get-ADGroup -Identity $GroupName -Properties member
$Group.member -contains $User.DistinguishedName
}
If size limit is your problem, you can use the DirectoryServer to retrieve a ranged result of the member attribute:
function Test-GroupMembership
{
[CmdletBinding()]
Param(
[string]$UserName,
[string]$GroupName
)
# Fetch User
$User = Get-ADUser -Identity $UserName
# return on failure
if(-not $User){
Write-Error -Message ('User "{0}" not found' -f $GroupName)
return $false
}
# Use DirectorySearcher to retrieve ranged member attribute
$GroupSearcher = '' -as [adsisearcher]
$GroupSearcher.Filter = '(&(objectClass=group)(name={0}))' -f $GroupName
$GroupSearcher.SearchScope = 'Subtree'
$GroupSearcher.SearchRoot = '' -as [adsi]
# AD reponds with at least 1500 values per multi-value attribute since Windows Server 2003
$Start = 1
$Range = 1500
$GroupMembers = #()
$HasMoreMembers = $false
# Keep retrieving member values until we've got them all
do{
# Use range operator to "page" values
# Ref: https://msdn.microsoft.com/en-us/library/aa367017(v=vs.85).aspx
$RangedMember = 'member;range={0}-{1}' -f $Start,$($Start + $Range - 1)
$GroupSearcher.PropertiesToLoad.Add($RangedMember) | Out-Null
# Retrieve group
$Group = $GroupSearcher.FindOne()
# return on failure
if(-not $Group) {
Write-Error -Message ('Group "{0}" not found' -f $GroupName)
return $false
}
# If we've reached the end of the member list,
# AD will return a property where the upper range
# value is *, so it might not be the same property
# name we specified in PropertiesToLoad
$ReturnedMember = #($Group.Properties.PropertyNames) -like 'member;*'
# Add all members to the $GroupMembers variable
foreach($member in $Group.Properties."$ReturnedMember") {
# Test if user is in the member list
if($member -eq $User.DistinguishedName){
return $true
}
}
# If we've reached the end, exit the loop
if($ReturnedMember -eq $RangedPropertyName){
$HasMoreMembers = $true
}
} while ($HasMoreMembers)
# User wasn't found
return $false
}
To provide a bit of consistency in user experience, please use Approved Verbs for command names in PowerShell (eg. Test-* instead of Check-*)
[adsisearcher] is a type accelerator for the DirectorySearcher class
import-module ActiveDirectory
(get-aduser "djohns02" -Properties AccountExpirationDate).AccountExpirationDate
This would work if the user had ADUC tools setup and configured on their machines. The problem is users will not have this setup and installed.
My next thought would be to try and setup a powershell script to run a remote script on a machine that does have this setup. Since they are not admins, they cannot get this setup and working as far as I know. As an admin I could force the trust and enable remote, but not as a standard user.
Are they any possibilities I am not thinking of? The goal is for contractors to be able to check when their account expires.
net user %USERNAME% /domain
My program can filter the results and give me just the expiration
Here is a PowerShell script you can use that does not require the AD cmdlets:
# Get-AccountExpirationDate.ps1
# Written by Bill Stewart
#requires -version 2
[CmdletBinding()]
param(
[parameter(Mandatory=$true,ValueFromPipeline=$true)]
[String[]] $Name
)
begin {
$ACCOUNT_NEVER_EXPIRES = 0x7FFFFFFFFFFFFFFF
$Searcher = [ADSISearcher] [ADSI] ""
$Searcher.PageSize = 1000
$Searcher.PropertiesToLoad.AddRange(#("accountexpires","distinguishedname","samaccountname"))
function Get-SearchResultProperty {
param(
[System.DirectoryServices.ResultPropertyCollection] $properties,
[String] $propertyName
)
if ( $properties[$propertyName] ) {
$properties[$propertyName][0]
}
}
function Get-AccountExpirationDate {
param(
$name
)
$Searcher.Filter = "(&(objectCategory=person)(objectClass=user)(anr=$name))"
$searchResults = $searcher.FindAll()
$resultCount = ($searchResults | measure-object).Count
if ( $resultcount -gt 0 ) {
foreach ( $searchResult in $searchResults ) {
$properties = $searchResult.Properties
$accountExpires = Get-SearchResultProperty $properties "accountexpires"
if ( $accountExpires -ne $null ) {
if ( ($accountExpires -eq 0) -or ($accountExpires -eq $ACCOUNT_NEVER_EXPIRES) ) {
$accountExpires = $null
}
else {
$accountExpires = [DateTime]::FromFileTime($accountExpires)
}
}
"" | select-object `
#{Name = "distinguishedName"; Expression = {Get-SearchResultProperty $properties "distinguishedname"}},
#{Name = "sAMAccountName"; Expression = {Get-SearchResultProperty $properties "samaccountname"}},
#{Name = "accountExpires"; Expression = {$accountExpires}}
}
}
$searchResults.Dispose()
}
}
process {
foreach ( $nameItem in $Name ) {
Get-AccountExpirationDate $nameItem
}
}
You can run it like this (assuming the script is in C:\Scripts):
PS C:\Scripts> .\Get-AccountExpirationDate.ps1 kendyer
I'm attempting to create a function for a script module that verifies the previous parameter has been set before it shows the next parameter.
The parameters I need are Identity, Share and Quota. I always want Identity to show, I don't want Share to show until the Identity has been set and I don't want Quota to show until the Share has been set.
I'm able to easily access $Identity, I'm not able to access $Share from within DynamicParam{}. I stepped through the script using PowerGUI and I was only able to see $Share when I hit Begin{}.
I have a way to workaround this by just showing Share/Quota if Identity is set, but ultimately I would like to learn how to keep adding additional parameters based on the previously set parameter.
A copy of the function is below. personfileutility.exe is just an executable we use to interact with the various systems to provision and gather information on a user.
function New-PersonalFiles
{
[CmdletBinding()]
Param
(
# Param1 help description
[Parameter(Mandatory=$true)]
$Identity
)
DynamicParam
{
$paramDictionary = new-object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
if (($Identity -notlike "") -or ($Identity -notlike $null)){
$attributes = new-object System.Management.Automation.ParameterAttribute
$attributes.ParameterSetName = "__AllParameterSets"
$attributes.Mandatory = $true
$lookup = [xml](\\servername\personalfiles\personfileutility.exe -a $Identity)
$type = $lookup.User.user_directory.type.type
if ($type -like "other" -or $type -like "staff") {
$arguments = #()
$arguments += "\\fileserver\sharename"
}
elseif ($type -like "faculty") {
$arguments = #()
$arguments += "\\fileserver\sharename"
$arguments += "\\fileserver\sharename2"
}
elseif ($type -like "student") {
$arguments = #()
$arguments += "\\fileserver2\sharename"
}
$ParamOptions = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $arguments
$attributeCollection = new-object -Type System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection.Add($attributes)
$attributeCollection.Add($ParamOptions)
$dynParam1 = new-object -Type System.Management.Automation.RuntimeDefinedParameter("Share", [String], $attributeCollection)
$paramDictionary.Add("Share", $dynParam1)
}
if (($Share -like "\\fileserver\*"))
{
$attributes2 = new-object System.Management.Automation.ParameterAttribute
$attributes2.ParameterSetName = "__AllParameterSets"
$attributes2.Mandatory = $true
$lookup = [xml](\\servername\personalfiles\personfileutility.exe -a $Identity)
$type = $lookup.User.user_directory.type.type
if ($type -like "other" -or $type -like "staff") {
$arguments = #()
$arguments += "15GB"
$arguments += "20GB"
}
elseif ($type -like "faculty") {
$arguments = #()
$arguments += "10GB"
$arguments += "15GB"
}
elseif ($type -like "student") {
$arguments = #()
$arguments += "5GB"
$arguments += "10GB"
}
$ParamOptions2 = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $arguments2
$attributeCollection2 = new-object -Type System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection2.Add($attributes2)
$attributeCollection2.Add($ParamOptions2)
$dynParam2 = new-object -Type System.Management.Automation.RuntimeDefinedParameter("Quota", [String], $attributeCollection2)
$paramDictionary.Add("Quota", $dynParam2)
}
return $paramDictionary
}
<#
.Synopsis
Short description
.DESCRIPTION
Long description
.EXAMPLE
Example of how to use this cmdlet
.EXAMPLE
Another example of how to use this cmdlet
#>
Begin
{
}
Process
{
\\servername\personalfiles\personfileutility.exe -a $Identity -c -q ((Invoke-Expression $PSBoundParameters.Quota) / 1KB) -s $Share
}
End
{
}
}
I'm trying to replicate the "workaround" you're using, but I'm only seeing that I have access to the dynamic parameter Share (not Quota). In order to reproduce your setup, since I don't have access to personfileutility.exe I have commented out two lines and added a third where I hardcode $type to "faculty". For example, in two places I have updated the code to be:
#$lookup = [xml](\\servername\personalfiles\personfileutility.exe -a $Identity)
#$type = $lookup.User.user_directory.type.type
$type = "faculty"
With these changes in place, I can access the Share parameter after I specify an Identity. However, I cannot access the Quota. Do you expect Quota to be available?
If I'm understanding the problem you're asking about, you do not want Share to be accessible until Identity is accessible (which you currently have working). But you in addition, you don't want Quota to be accessible until both Identity and Share are filled out and you don't know how to make that work. Is that correct?
If I'm understanding the problem correctly, I don't believe that PowerShell offers a mechanism to achieve that with Commandlet binding. I think you could make that work by either using a GUI application, or interactively prompting the user for inputs.