Good afternoon Stack Overflow.
I am trying to automate some paper based processes using Powershell. We already had in place a Powershell solution where the output was an Excel Spreadsheet, but for planning for the future we felt that a CSV output was easier to work with than Excel.
What we are doing is pretty basic, we are reaching out to the owners of groups in AD, specified in a text file. For each group listed, we're looping through and adding the items to the table. Here's the problem, I am still very new to Powershell and am having issues looping through the 'for each' statements cleanly.
I am experiencing the following error:
Exception calling "Add" with "1" argument(s): "This row already belongs to this table."
At line:77 char:17
+ $table2.Rows.Add <<<< ($row2)
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
I feel it's appropriate to share the code I am working with:
import-module activedirectory
$strGroupList = get-content "C:\Users\MYUSERNAME\Documents\Group Audit\audit.txt"
$strDate = Get-Date
foreach ($strGroup in $strGroupList)
{
$strGroupMember = Get-aduser -Identity $Group -property description
$tabName = "GroupAuditTable"
#Create the Table Object
$table = New-Object system.Data.DataTable "$tabName"
$table2 = New-Object system.Data.DataTable "$tabName2"
#define columns for row 1
$col1 = New-Object system.Data.DataColumn $strGroup,([string])
$col2 = New-Object system.Data.DataColumn $strDate,([string])
#define columns for row 2
$col3 = New-Object system.Data.DataColumn Name,([string])
$col4 = New-Object system.Data.DataColumn Description, ([string])
$col5 = New-Object system.Data.DataColumn NetworkID, ([string])
$col6 = New-Object system.Data.DataColumn Nested, ([string])
#Add the columns to row 1
$table.columns.add($col1)
$table.columns.add($col2)
#Add the columns to row 2
$table2.columns.add($col3)
$table2.columns.add($col4)
$table2.columns.add($col5)
$table2.columns.add($col6)
#create a row
$row = $table.NewRow()
$row2 = $table2.NewRow()
#create an additional row for the titles of columns
$rowTitles = $table.NewRow()
$rowTitles2 = $table2.NewRow()
$strGroupDetails = Get-ADGroupMember -identity $strGroup
foreach ($Group in $strGroupDetails)
{
########################################################################## Check for nested groups
if($Group.ObjectClass -eq "group")
{
$strGroupDetails = Get-ADGroupMember -identity $Group #-Recursive
$strNestedGroupName = $Group.name
########################################################################## List members of nested groups
foreach($strNestedGroup in $strGroupDetails)
{
#$strNestedGroupName = $Group.name
$strGroupMember = Get-aduser -Identity $StrNestedGroup -property description
$row2.Name = $strGroupMember.name
$row2.Description = $strGroupMember.description
$row2.NetworkID = $strGroupMember.SamAccountName
$row2.Nested = "(From NESTED GROUP: " + $strNestedGroupName
$table2.Rows.Add($row2)
}
}
else
{
#enter data into row 2
$row2.Name = $strGroupMember.name
$row2.Description = $strGroupMember.description
$row2.NetworkID = $strGroupMember.SamAccountName
$row2.Nested = "Not Nested"
#Add the row to the table
$table.Rows.Add($row)
#Add the row to the second table
$table2.Rows.Add($row2)
#Display the table
$table | format-table -AutoSize
$table2 | format-table -AutoSize
}
}
}
$tabCsv = $table | export-csv C:\Users\MYUSERNAME\Desktop\Audit\GroupAudit.csv -noType
$tabCsv2 = $table2 | export-csv C:\Users\MYUSERNAME\Desktop\Audit\GroupAudit_2.csv -noType
That's it, pretty basic stuff.
The error is on line #77:
$table2.Rows.Add($row2)
This worked before I stuck the 'Nested Groups' For Each loop in there, and I am just puzzled as to why that would break it, unless I am missing something obvious here. When I remove this:
foreach ($Group in $strGroupDetails)
{
########################################################################## Check for nested groups
if($Group.ObjectClass -eq "group")
{
$strGroupDetails = Get-ADGroupMember -identity $Group #-Recursive
$strNestedGroupName = $Group.name
########################################################################## List members of nested groups
foreach($strNestedGroup in $strGroupDetails)
{
#$strNestedGroupName = $Group.name
$strGroupMember = Get-aduser -Identity $StrNestedGroup -property description
$row2.Name = $strGroupMember.name
$row2.Description = $strGroupMember.description
$row2.NetworkID = $strGroupMember.SamAccountName
$row2.Nested = "(From NESTED GROUP: " + $strNestedGroupName
$table2.Rows.Add($row2)
}
}
else
{
#enter data into row 2
Things work as expected, what am I missing?
You are running a ForEach and the first thing is an If clause where you define $strGroupMember, which you reference in the Else clause for the same If/Then. Here, this is simplified:
But you don't define $strgroupmember in the else scriptblock at all. So it's then adding the last one from the last group, and it already has that entry, so it throws an error.
With fake data.... Group is Administrators, it contains 1 group ("Payroll" which has 1 member, "John") and 2 users ("Jim" and "Sally").
Hopefully that all made sense. Or at least enough of it that you see the issue. The fix? Add one line to your Else scriptblock:
Edit: I'd make an array, create user object and add them to the array, and then iterate through nested groups and add that group to each user object that it applies to, then push that data into a table so you'd end up with something like:
But that's me.
Edit2: Ok, round 2! I see changes, but there's still some issues for sure. The issue you are coming across is adding rows to the table. Ok, understandable. Here's some issues I see:
Ok, for each item in the nested group query AD and store the results in
$strGroupMember
. Then update$row2.nested
to the current group's name, and add$row2
to the table as a new row. So let's just say we enter this loop with$row2
being this:Ok, it's looping through the entries that it pulled for the nested group "Power Users". We'll make this easy and assume there are not additional groups nested within this nested group, that's a whole other issue.
Item 1 on that list is Marcia Winkle, she has an AD description of "Payroll", and a user name of MWinkle. The loop found that out and assigned all her info to
$strGroupMember
.Next it updates
$row2
, and replaces theNested
value, replacing "Not Nested" with "From nested group: Power Users" (the group we are working with).Then it adds
$row2
to$table2
, creating a row:Ok, done! Next member of the group is Ethel Jones. She too has the description of Payroll, and her user name is EJones. The loop pulled that from AD, and replaced any existing values of
$strGroupMember
with them. Not that it matters, we didn't seem to ever use any of that info in the last loop anyway, but that's not the script's concern, it just powers through!Next, we update
$row2.Nested
again, changing "From nested group: Power Users" to "From nested group: Power Users" ...huh, same thing as before, but ok, you're the boss. Moving on!Now it attempts to add another row to the table, but here's where we get an error: It's the exact same data as last time since nothing in $row2 was really changed, it's still:
You see where I'm going here? Next issue I see is that you are doing an If/Then/Else cycle here, but you are trying to add rows in the Then section, but not in the Else section. Here, take this ideology and update your code accordingly:
This should clear up your current errors. It doesn't recurse more than one level of groups, so you will miss people if there's a group within a group, within a group. As in:
That will include the first nested group, and the second nested group, but unless he is represented somewhere else Billy Mac will not make it in your list.
As for my solution, I had most of this written up last night so I finished it up today. This should do what you want I think. It creates an empty array, then populates it with an object for every user found recursively. Then it cycles through all nested groups and adds that group's name to the Nested property of the object of each user in it. Then it outputs that array as a CSV file for each group processed.