Add Content formatting is wrapping the text.

I have a script that looks through an OU and lists all Security Groups, and the total members of each.

Import-Module ActiveDirectory
$groups = Get-ADGroup -f * -searchbase 'OU to search here'
    foreach ($group in $groups) {
    $groupname=($group.name)
    $membercount=@(Get-ADGroupMember $groupname).count
    $strdone = ($group.name),',',$membercount
    Add-content c:\outputfiles\CountofUsersinEachGroup.txt -value $strdone
}

The output should look like:
SecGroup1,13
SecGroup2,1
SecGroup3,11
SecGroup4,4
SecGroup5,5

But instead, it looks like the follwing:
SecGroup1
,
13
SecGroup2
,
1
SecGroup3
,
11
SecGroup4
,
4

I’ve used Add-Content many times in other scripts in the past and this is the first time it is “wrapping” to a new line.

My other scripts that use Add-Content are working fine (I checked today) so I’m not sure why the formatting is “off” on this one script.

If someone could let me know what I missed I’d appreciate it.

Its this line
$strdone = ($group.name),‘,’,$membercount

that’s causing the problem. You need to concatenate the two strings so either use

$strdone = ($group.name) + ‘,’ + $membercount

OR
$strdone = “$($group.name),$membercount”

A comma won’t concatenate you strings. The data is being treated as an array of strings which why you are getting the data split across multiple lines

Bah! /me has teh dumb!

It was staring me in the face all morning! I was looking through my other scripts and they are all like your example

$strdone = ($blah) +‘,’+ ($blah1) and I completely overlooked the + signs,lol

Okay I tried some changes suggested by others and came up with the following (99% not my code because a poster explained how to use $props via the example code below)

$groups = Get-ADGroup -f * -searchbase 'My OU Here'
foreach ($group in $groups) {
$members=Get-ADGroupMember $groupname | Get-ADUser -Property *
$props=@{
	GroupName=$group.name
	MemberCount=$members.count
	InactiveCount=@($members|?{$_.lockedout -or $_.Enabled -eq $false}).count
	}
New-Object PsObject -Property $props | Format-Table -AutoSize
}

When I run this, I get only about 1/10th of the group information. The rest (vast majority) throw up errors during the Get-ADGroupmember or Get-ADUser section like the following:

(from the Get-ADGroupmember command) + $members=Get-ADGroupMember $groupname | Get-ADUser <<<< -Property *
+ CategoryInfo : ResourceUnavailable: (CN=john.q.pub…,DC=company,DC=com:ADUser)
[Get-ADUser], ADReferralException

(from the Get-ADUser command) : ResourceUnavailable: (CN=norman.prese…,DC=com,DC=:ADUser)

I’m not sure why the errors appear because when I execute the commands manually, I get good data

$m=Get-ADGroupMember SecurityGroupName  | Get-ADUser -Property *
$m | select Name (will list all 47 members of group)
$i=($m|?{$_.lockedout -or $_.enabled -like 'false'}).count
PS F:\> $i
PS F:\> 27 (this is how many accts in this group are disabled or locked out)
PS F:\> $m.count
PS F:\> 47 (this is how many total accts are in this group)

Your logic isn’t identical in the script and manual steps

script:
InactiveCount=@($members|?{$.lockedout -or $.Enabled -eq $false}).count

manual:
$i=($m|?{$.lockedout -or $.enabled -like ‘false’}).count

As an aside don’t use aliases for cmdlets and parameters in scripts. if a puppy is killed every time you use write-host you don’t want to see what happens when aliases are used in scripts.

You don’t need to use -property * on Get-ADuser you can list the extra properties you need. It’ll cut down the data returned.

In this line
InactiveCount=@($members|?{$.lockedout -or $.Enabled -eq $false}).count

why do you need the @

$members is already an array and you are just filtering the members

The errors are probably due to AD not understanding what you are trying to do. Piping the output of get-adgroupmember into get-aduser should work

[quote=9758]Your logic isn’t identical in the script and manual steps
<P></P>
<P>script:<BR>InactiveCount=@($members|?{$.lockedout -or $.Enabled -eq $false}).count</P>
<P>manual:<BR>$i=($m|?{$.lockedout -or $.enabled -like ‘false’}).count</P>
<P>As an aside don’t use aliases for cmdlets and parameters in scripts. if a puppy is killed every time you use write-host you don’t want to see what happens when aliases are used in scripts.</P>
<P>You don’t need to use -property * on Get-ADuser you can list the extra properties you need. It’ll cut down the data returned.</P>
<P>In this line<BR>InactiveCount=@($members|?{$.lockedout -or $.Enabled -eq $false}).count</P>
<P>why do you need the @</P>
<P>$members is already an array and you are just filtering the members</P>
<P>The errors are probably due to AD not understanding what you are trying to do. Piping the output of get-adgroupmember into get-aduser should work</P>[/quote]

It seems I pasted the wrong code into the post. Sorry.

I am using the following code:

Import-Module ActiveDirectory
$groups = Get-ADGroup -f * -searchbase 'My OU Here'
    foreach ($group in $groups) {
$member=Get-ADGroupmember $group | Get-ADuser -property *
$props=@{
    groupname=$group.name
    Members=$member.count
    Inactive=($member | Where {$_.Enabled -eq 'False'}).count
    }
    New-Object PsObject -property $props
 }

This code seems to work. However, I have encountered some issues I am not knowledgeable enough to handle.

Specifically, My OUs are set up with Groups in a Resource container under my main OU and my Users are in a Users container. When the script pipes the Get-ADgroupmember to Get-ADUser and encounters someone’s account that resides in another OU (or even another domain in our forest) it throws errors for that user account (but does continue to run)

My Groups container is:

OU=Resource Groups,OU=Groups,OU=City,OU=state,DC=EastDomain,DC=Headquarters,DC=company,DC=com

My Users container is:

OU=USERS,OU=CITY,OU=STATE,DC=EastDomain,DC=Headquarters,DC=Company,DC=com

I guess my questions would be:

Is it possible to look for the groups in the Resource Groups container (for the initial get-adgroup command) and then, when get-adgroupmember is executed and starts listing the user names, look up those member accounts in the Users container?

In essence, the code would look something like: (if this were possible)

Import-Module ActiveDirectory
$groups = Get-ADGroup -f * -searchbase 'OU=Resource Groups,OU=Groups,OU=City,OU=state,DC=EastDomain,DC=Headquarters,DC=company,DC=com'
    foreach ($group in $groups) {
$member=Get-ADGroupmember $group | Get-ADuser -f * OU=USERS,OU=CITY,OU=STATE,DC=EastDomain,DC=Headquarters,DC=Company,DC=com -property *
$props=@{
    groupname=$group.name
    Members=$member.count
    Inactive=($member | Where {$_.Enabled -eq 'False'}).count
    }
    New-Object PsObject -property $props
 }

It shouldn’t matter what OU the users are in, but if the groups contain members from other domains, that’s where you might start to see AD Referral exceptions.

This is mostly guesswork, for the moment, since I don’t have a multi-domain test environment set up. However, I’m guessing that when you make a single call to Get-ADUser, it connects to a single domain controller and tries to process all of the objects in the pipeline against that DC (and the cmdlet isn’t enabling ADSI’s “chase referrals” option under the hood). So if you pipe distinguished names from multiple domains, some of them will wind up getting referrals instead of actual objects. You can probably solve this by using the DirectorySearcher and DirectoryEntry classes instead of the AD cmdlets, which gives you control over the referral chasing behavior. You might also be able to get around the problem by making a separate call to Get-ADUser for each group member.

Here’s a modification of the code that tries the second approach, making a separate call to Get-ADUser for each group member. See how it works:

Import-Module ActiveDirectory
$groups = Get-ADGroup -f * -searchbase 'My OU Here'

foreach ($group in $groups) {
    $inactiveUsers = 0

    $members = Get-ADGroupMember $group
    foreach ($member in $members)
    {
        if ($member.objectClass -eq 'user')
        {
            try
            {
                $user = Get-ADUser -Identity $member.DistinguishedName -ErrorAction Stop

                if ($user.Enabled -eq $false)
                {
                    $inactiveUsers++
                }
            }
            catch
            {
                Write-Error -ErrorRecord $_
            }
        }
    }

    $props=@{
        groupname = $group.name
        Members = $members.count
        Inactive = $inactiveUsers
    }

    New-Object PsObject -property $props
}

[quote=9771]It shouldn’t matter what OU the users are in, but if the groups contain members from other domains, that’s where you might start to see AD Referral exceptions.
<P></P>
<P>This is mostly guesswork, for the moment, since I don’t have a multi-domain test environment set up. However, I’m guessing that when you make a single call to Get-ADUser, it connects to a single domain controller and tries to process all of the objects in the pipeline against that DC (and the cmdlet isn’t enabling ADSI’s “chase referrals” option under the hood). So if you pipe distinguished names from multiple domains, some of them will wind up getting referrals instead of actual objects. You can probably solve this by using the DirectorySearcher and DirectoryEntry classes instead of the AD cmdlets, which gives you control over the referral chasing behavior. You might also be able to get around the problem by making a separate call to Get-ADUser for each group member.</P>
<P>Here’s a modification of the code that tries the second approach, making a separate call to Get-ADUser for each group member. See how it works:</P>

Import-Module ActiveDirectory
$groups = Get-ADGroup -f * -searchbase ‘My OU Here’

foreach ($group in $groups) {
$inactiveUsers = 0

$members = Get-ADGroupMember $group
foreach ($member in $members)
{
    if ($member.objectClass -eq 'user')
    {
        try
        {
            $user = Get-ADUser -Identity $member.DistinguishedName -ErrorAction Stop

            if ($user.Enabled -eq $false)
            {
                $inactiveUsers++
            }
        }
        catch
        {
            Write-Error -ErrorRecord $_
        }
    }
}

$props=@{
    groupname = $group.name
    Members = $members.count
    Inactive = $inactiveUsers
}

New-Object PsObject -property $props

}

[/quote]

Dave,

Thanks for the response!

Using your code I see errors thrown when the script tries to get-aduser for someone located in our western region domain. (expected)

New-Object PsObject -property $props
} : Cannot find an object with identity: 'CN= john.public,OU=USERS,OU=City,OU=State,DC=WestRegion,DC=headquarters,D
C=company,DC=com under: 'DC=EastRegion,DC=headquarters,DC=company,DC=com'.
    + CategoryInfo          : ObjectNotFound: (CN=john.public...,DC=company,DC=com:ADUser) [Write-Error], ADIdentityNotFoundException
    + FullyQualifiedErrorId : Cannot find an object with identity: 'CN= john.public,OU=USERS,OU=city,OU=state,DC=westregion,DC=headquarters,DC=company,DC=com' under: 'DC=eastregion,DC=headquarters,DC=company,DC=com'.

This does not stop the script from continuing to run and I am totally okay with that :slight_smile:

Some notes on the output:

When there are zero inactive accounts assigned to a group, the output is a ‘0’ as would be expected.
Example:
0 SecGroupA 4
When the group has no members, there is no output. in essence, the number under the “members” column for that group is ‘’ (null)
Example:
0 SecurityGroupName
When a group has only other groups as members, the output is {} (literally a pair of curly brackets)
Example:
0 SecurityGroup1 {}

OK, looks like Get-ADUser is not behaving like I had hoped (searching other domains if the DN you pass it isn’t in the local domain). I’m sure that can be tweaked with some combination of the -Server, -Partition and/or -SearchBase arguments, but at this point I would probably just fall back to using ADSI instead of the AD cmdlets, if it were my script (because I’m more familiar with how it behaves).

Here’s an easy workaround for the quirky behavior of $members.Count:

Import-Module ActiveDirectory
$groups = Get-ADGroup -f * -searchbase 'My OU Here'

foreach ($group in $groups) {
    $inactiveUsers = 0
    $memberCount = 0
    
    $members = Get-ADGroupMember $group
    foreach ($member in $members)
    {
        $memberCount++
        
        if ($member.objectClass -eq 'user')
        {
            try
            {
                $user = Get-ADUser -Identity $member.DistinguishedName -ErrorAction Stop

                if ($user.Enabled -eq $false)
                {
                    $inactiveUsers++
                }
            }
            catch
            {
                Write-Error -ErrorRecord $_
            }
        }
    }

    $props=@{
        groupname = $group.name
        Members = $memberCount
        Inactive = $inactiveUsers
    }

    New-Object PsObject -property $props
}