Reputation: 33
I'm attempting to run a query against AD to give me a list of users. Since our organisation adds the tag "LastName, FirstName" to the description field of computer objects, I also want to list out the assigned machines per user. This is what I have so far:
$ADGroup = "SomeADGroup"
$UserList = Get-ADGroupMember -Identity $ADGroup -Recursive `
| Select SamAccountName `
| foreach-object {(Get-ADUser -Identity $_.SamAccountName `
| Select @{n='AssignedUser';e={$_.Surname + ", " + $_.GivenName}}).AssignedUser}
$Results = New-Object System.Collections.Generic.List[System.Object]
foreach ($User in $UserList)
{
$MachineList = @(((Get-ADComputer -Filter "Description -like `"*$User*`"" | Select Name).Name) -join $_ + ",").ToUpper()
foreach ($Machine in $MachineList)
{
$Results +=
@{
'User' = $User
'Machine' = $Machine
}
}
}
$Results | ForEach-Object { [pscustomobject] $_ } | Format-Table
The result is something like this:
User Machine
---- -------
White, Scott W107175
Jones, Henry W107195
Flinstone, Fred L109531,W108812
Since it's possible to have multiple machines assigned to each user, I'm trying to figure out a way to dynamically add "columns" to the array so that each machine name element resides in a column. This will eventually make it easier to export to CSV. I'm looking for output similar to this:
User Machine Machine2
---- ------- -------
White, Scott W107175
Jones, Henry W107195
Flinstone, Fred L109531 W108812
Upvotes: 2
Views: 10700
Reputation: 33
$ADGroup = "Some AD Group"
$UserList = Get-ADGroupMember -Identity $ADGroup -Recursive | Get-ADUser
$Results = @()
$MaxMachineCount = 0
foreach ($User in $UserList)
{
if ($User.SurName -and $User.GivenName -ne $null)
{
$CurrentUser = [ordered]@{ 'User' = $User.SurName + ", " + $User.GivenName }
$MachineList = @(((Get-ADComputer -Filter "Description -like `"*$($CurrentUser.User)*`"") | Select -ExpandProperty Name))
for ($i=0; $i -lt $MachineList.Count; $i++)
{
$CurrentUser.Add("Machine $i","$($MachineList[$i].ToUpper())")
if ($MaxMachineCount -le $i) {$MaxMachineCount++}
}
$Results += [PSCustomObject]$CurrentUser
}
}
for ($i=0; $i -lt $MaxMachineCount; $i++){$Results[0] | Add-Member -Name "Machine $i" -Value $null -MemberType NoteProperty -ErrorAction Ignore}
$Results | Format-Table -AutoSize
The end piece of code Format-Table -AutoSize
can also be substituted for ConvertTo-CSV | Out-File "C:\Some\Directory\File.csv"
I added this to create additional columns in my result. Previously, only a maximum of two machines per user would show. Now, the script returns ALL assigned machines per user.
for ($i=0; $i -lt $MachineList.Count; $i++){$Results[0] | Add-Member -Name "Machine $i" -Value $null -MemberType NoteProperty -ErrorAction Ignore
This is my result:
User Machine 0 Machine 1 Machine 2 Machine 3 Machine 4 Machine 5 Machine 6 Machine 7 Machine 8
---- --------- --------- --------- --------- --------- --------- --------- --------- ---------
Froeman, Abe L110911
Maclatchie, Matthew W109370 L108518
White, Walter V999100 L107800 V999109 V999108 V999107 V999102 V999106 V999105 V999101
Thank you to @TessellatingHeckler and @TheMadTechnician for your input.
Upvotes: 0
Reputation: 29048
You're well into "variable variable" question territory here; fundamentally you would never write
$User.Machine1
$User.Machine2
$User.Machine...
because you don't know how many machines there are, so you cannot write the property names in your code. Arrays are the answer to this, and you have one variable in your code which references an array, with all the machines in it. Then your code makes sense, it's easy and sensible and works, but it doesn't align with your dream of CSV - because your data is not CSV-shaped.
If you add one property per machine to each user, you get some users with one machine and some with four ... and that trips up the likes of Format-Table
, ConvertTo-Csv
, Out-GridView
, because they take the first object they see as the template, and then any columns on other objects which weren't on the first object, they drop.
So you have to do what TheMadTechnician is doing, and go back and dynamically adding all the blank MachineN properties to all the objects, and it is enormously simpler if you just say up front "there will be N columns".
Then you can count through them, and I get this:
$ADGroup = "SomeADGroup"
$ColumnCount = 10
Get-ADGroupMember -Identity $ADGroup -Recursive | Get-ADUser | ForEach-Object {
$User = [ordered]@{ 'User' = $_.SurName + ", " + $_.GivenName }
$Machines = @((Get-ADComputer -Filter "Description -like '*$($User.User)*'").Name)
for ($i=0; $i -lt $ColumnCount; $i++)
{
$User["Machine$i"] = $Machines[$i]
}
[PSCustomObject]$User
} | ConvertTo-Csv
NB. I'm relying on array out of bounds behaviour. If you need to avoid that, then add 10 columns of empty string onto the end of the Machines list as a buffer.
But I'd still be very tempted to just mash out a CSV myself, with string manipulation, and not go through objects at all, since you're not using them as they 'should' be used, but only going a long way around making the right properties only to throw them away to get a CSV.
Off-topic, your code is very convoluted:
$UserList = Get-ADGroupMember -Identity $ADGroup -Recursive `
| Select SamAccountName `
| foreach-object {(Get-ADUser -Identity $_.SamAccountName `
| Select @{n='AssignedUser';e={$_.SurName + ", " + $_.GivenName}}).AssignedUser}
You get the AD Group Member, then you select the SamAccountName in a way that does absolutely nothing, then you loop over the results instead of pipelining them directly into Get-ADUser, for no benefit, then you select a calculated property and then throw it away and get the value. Instead:
Get-ADGroupMember -Identity $ADGroup -Recursive | Get-ADUser | ForEach-Object { $_.SurName + ', ' + $_.GivenName }
Get the group members, get their AD Users, and compute your string from those results.
and
$Results = New-Object System.Collections.Generic.List[System.Object]
->
$Results = @()
or, better yet:
$Results = @()
foreach ($x in $y) {
$Results += ...
}
->
$Results = foreach ($x in y ) {
...
}
and in
| ForEach-Object { [pscustomobject] $_ } |
You ... could have made them PSCustomObject
s when you were making them, and then you wouldn't need this.
Upvotes: 1
Reputation: 36332
Ok, so you want to add an unknown number of properties to an existing object. That's not that hard to do using the Add-Member
cmdlet. Next you just need to structure a loop so that adding those properties is easy to understand, which we can do using a For
loop. Lastly you need to make sure that the first record in your array has all of the potential properties. I'll explain that a little more when we get there.
First, we get the users. I changed what you had a little, because I'm strictly opposed to using the backtick as a line continuation character since it is so easily foiled by whitespace. I also changed $UserList
from an array of strings to an array of objects, since you want objects later anyway. We capture the first and last name still, but as the User
property on a [PSCustomObject]
.
$ADGroup = "SomeADGroup"
$UserList = Get-ADGroupMember -Identity $ADGroup -Recursive |
Select SamAccountName |
foreach-object {
Get-ADUser -Identity $_.SamAccountName |
Select @{n='User';e={$_.Surname + ", " + $_.GivenName}}
}
So now we have an array of objects with one property. Now we loop through that, do the search for computers, and capture the machines as an array of strings. That's just slightly modified code from what you had already.
foreach ($User in $UserList)
{
$MachineList = Get-ADComputer -Filter {Description -like "*$($User.User)*"} | Select -ExpandProperty Name
Now is where we change things up a little. For each machine that we found we need to add a property to that object we already have. Since we don't know how many that is we can just use a For
loop and number the properties accordingly.
for($i=0;$i -le $MachineList.Count;$i++)
{
Add-Member -InputObject $User -NotePropertyName "Machine$($i+1)" -NotePropertyValue $MachineList[$i]
}
}
So now $UserList
is an array of objects, each of which has the User
property, and however many Machine#
properties as is appropriate to that user. The problem is that PowerShell outputs arrays of objects based on the first object in the array. So in your example:
User Machine Machine2
---- ------- -------
White, Scott W107175
Jones, Henry W107195
Flinstone, Fred L109531 W108812
The Machine2
column will never be displayed because that property doesn't exist on the first record for Scott White. So to get around this we need to find out all of the possible properties for any given object in the array, and add any missing ones to the first record. This is easily done using the hidden PSObject
property that each object in PowerShell has.
First we get a list of all possible property names:
$AllProps = $UserList | ForEach{$_.PSObject.Properties.Name} | Select -Unique
Then we filter out any properties already on the first record, and anything left gets added to the record as a new property using the Add-Member
cmdlet again.
$AllProps |
Where{ $_ -notin $UserList[0].psobject.properties.name } |
ForEach{ Add-Member -InputObject $UserList[0] -NotePropertyName $_ -NotePropertyValue $null }
Then there's nothing to do but output the results, which are already stored in $UserList
since we made our list of users, and simply updated that existing list with each user's machines, rather than making a list of users, and then a new list of users plus machines.
$UserList | Format-Table
Upvotes: 3