Output repeats several times

This topic contains 9 replies, has 3 voices, and was last updated by Profile photo of VirGnar VirGnar 2 years, 2 months ago.

  • Author
    Posts
  • #19020
    Profile photo of VirGnar
    VirGnar
    Participant

    I'm trying to work with foreach loops to collect info and then outside the loops do an output of the final results, but it's outputting in an unusual manner. First it outputs the first listed server's results, but it outputs the first group on that server 3 times and the next group only once. Then after that it repeats output of each group/computer 20 times. I tried doing an Out-Null on the last command in the most internal loop to see if that was causing it but no change.

    $groups = "Administrators", "Users"
    $computers = "Server1", "Server2"
    
    foreach ($computer in $computers)
    {
        foreach ($group in $groups)
        {
            $Query = "SELECT * FROM Win32_GroupUser WHERE GroupComponent=`"Win32_Group.Domain='$computer',Name='$group'`""
            
            $UserObject += Get-CimInstance -ComputerName $computer -Query $Query | Out-Null
    
        }
    }
    
    $format = @{Expression={$_.GroupComponent.Name};Name='Group'}, @{Expression={$_.PartComponent.Name};Name='User'}, @{Expression={$_.PSComputerName};Name='Computer'}
    
    $UserObject | Select-Object -Property $format | Format-Table -Property User, Group, Computer  -GroupBy Computer
    

    The script is rather sloppy as it's just to test functionality right now before I clean up.

    As always, a preemptive thanks for any assistance.

  • #19021
    Profile photo of Don Jones
    Don Jones
    Keymaster

    This is a very common approach in formal programming. It's not always a good approach for PowerShell.

    I would typically prefer to write my code as a function, and have it output one object at a time to the pipeline. I can then pipe those results to whatever I like to format them, for example. Your way uses a lot more memory, for example, and it's harder to debug (at least for me). If you were outputting your own objects, there wouldn't be a need to use Select – you'd get what you wanted from the get-go, and have a bit more control over the execution flow.

    If I were debugging your code, I would probably add some output message, perhaps using Write-Verbose, so that I could see what computer it was working on, what group it was working on, and what queries it was running. Just looking at your code, it's tough to see any logic errors – I'd have to run it, which I'm not able to do for you.

  • #19022
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    I haven't tested your code, but it looks like you should have one line of output for each user account member of each group. If you're seeing 3 lines for the first group and one line for the second, that should mean there are 3 users who are members of the first group, one user who's a member of the second, and so on.

  • #19033
    Profile photo of VirGnar
    VirGnar
    Participant

    @Don:

    I'm trying to figure that out, but I don't know the best way to approach it using foreach loops to add to the object. If I create the object after the loops and just slap the whole $UserObject into it like so:

    $objecthash = @{'Group'=$UserObject.GroupComponent.Name;
                    'User'=$UserObject.PartComponent.Name;
                    'Computer'=$UserObject.PSComputerName}
    
    $FinalObject = New-Object -TypeName PSObject -Property $objecthash

    They all just get added as one instance:

    $FinalObject | Format-Table
    
    Group                                                               User                                            
    -----                                                               ----                                            
    {Administrators, Administrators, Administrators, Administrators...} {Administrator, Admin1, Admin2, Domain...
    

    I think I know what I need to do: create object first, then in each pass of the loop add the query results into the object as an instance, but I'm not sure how to do that properly.

    (b)@Dave:(/b)

    The output seems to be in groups, hence the query appears to collect all members from a particular group prior to being placed into the $UserObject var. Here's the first section of the output I'm referring to:

    Computer: Server1
    
    User                Group          Computer 
    ----                -----          -------- 
    Administrator       Administrators Server1
    Admin1              Administrators Server1
    Superuser           Administrators Server1
    Domain Admins       Administrators Server1
    Admin2              Administrators Server1
    Admin3              Administrators Server1
    Administrator       Administrators Server1
    Admin1              Administrators Server1
    Superuser           Administrators Server1
    Domain Admins       Administrators Server1
    Admin2              Administrators Server1
    Admin3              Administrators Server1
    Administrator       Administrators Server1
    Admin1              Administrators Server1
    Superuser           Administrators Server1
    Domain Admins       Administrators Server1
    Admin2              Administrators Server1
    Admin3              Administrators Server1
    INTERACTIVE         Users          Server1
    Authenticated Users Users          Server1
    Domain Users        Users          Server1
    
    
       Computer: Server2
    
    User                  Group          Computer    
    ----                  -----          --------    
    Administrator         Administrators Server2
    Admin2                Administrators Server2
    Admin1                Administrators Server2
    Domain Admins         Administrators Server2
    Backup_Administrators Administrators Server2
    Admin3                Administrators Server2
    INTERACTIVE           Users          Server2
    Authenticated Users   Users          Server2
    Domain Users          Users          Server2
    
    
       Computer: Server1
    
    User                Group          Computer 
    ----                -----          -------- 
    Administrator       Administrators Server1
    Admin1              Administrators Server1
    Superuser           Administrators Server1
    Domain Admins       Administrators Server1
    Admin2              Administrators Server1
    Admin3              Administrators Server1
    INTERACTIVE         Users          Server1
    Authenticated Users Users          Server1
    Superuser           Users          Server1
    Domain Users        Users          Server1
    
    
       Computer: Server2
    
    User                  Group          Computer    
    ----                  -----          --------    
    Administrator         Administrators Server2
    Admin2                Administrators Server2
    Admin1                Administrators Server2
    Domain Admins         Administrators Server2
    Backup_Administrators Administrators Server2
    Admin3                Administrators Server2
    INTERACTIVE           Users          Server2
    Authenticated Users   Users          Server2
    Domain Users          Users          Server2
    
    ...
    
    

    Notice the strange tripled output in the first -GroupBy grouped entries. After that, the group entries repeat 20 times each.

  • #19035
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    Does it do this consistently, every time? Or does the output keep growing the more times you run it?

    The reason I ask is that you're not re-initializing the $UserObject variable to an empty array. If this code happens to be executing up in the global scope, then you'll keep appending to the previous results each time you run the code. You could avoid that by adding the following command just before your foreach loops, or by not using an array to collect the data at all, as Don mentioned.

    $UserObject = @()
    

    If that's still not behaving, then I'll need to try to debug and reproduce the problem later to get a feel for what's happening.

    Edit: You may also need to get rid of the Out-Null.

  • #19036
    Profile photo of VirGnar
    VirGnar
    Participant

    *slap* What another glaring oversight. Of course it would just keeping building up with each run! I've made the change and it's all good.

    Though to be honest, I really would like the preferred method of using an object, but again I still don't know the answer to that. 🙁

  • #19037
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    Try this. I've just rearranged some of your code, placing the foreach loops and Select-Object call into a function. The only real difference is that I got rid of the $UserObject variable, and just allowed the objects to pass down the function's output stream right away.

    function Get-GroupMembers
    {
        [CmdletBinding()]
        param (
            [string[]] $ComputerName,
            [string[]] $Group
        )
    
        $properties = @(
            @{Expression={$_.GroupComponent.Name};Name='Group'},
            @{Expression={$_.PartComponent.Name};Name='User'},
            @{Expression={$_.PSComputerName};Name='Computer'}
        )
    
        foreach ($computer in $ComputerName)
        {
            foreach ($groupName in $Group)
            {
                $Query = "SELECT * FROM Win32_GroupUser WHERE GroupComponent=`"Win32_Group.Domain='$computer',Name='$groupName'`""
                Get-CimInstance -ComputerName $computer -Query $Query |
                Select-Object -Property $properties
            }
        }
    }
    
    $groups = "Administrators", "Users"
    $computers = "Server1", "Server2"
    
    Get-GroupMembers -ComputerName $computers -Group $groups |
    Format-Table -Property User, Group, Computer -GroupBy Computer
    

    It's fine to use Select-Object to create your custom objects, if you prefer. You can also use other syntax, such as New-Object, or the [pscustomobject] accelerator (which would require PowerShell 3.0 or later.) Here's an example of what that might look like:

    function Get-GroupMembers
    {
        [CmdletBinding()]
        param (
            [string[]] $ComputerName,
            [string[]] $Group
        )
    
        foreach ($computer in $ComputerName)
        {
            foreach ($groupName in $Group)
            {
                $Query = "SELECT * FROM Win32_GroupUser WHERE GroupComponent=`"Win32_Group.Domain='$computer',Name='$groupName'`""
                $cimInstances = Get-CimInstance -ComputerName $computer -Query $Query
    
                foreach ($cimInstance in $cimInstances)
                {
                    [pscustomobject] @{
                        Group    = $cimInstance.GroupComponent.Name
                        User     = $cimInstance.PartComponent.Name
                        Computer = $cimInstance.PSComputerName
                    }
                }
            }
        }
    }
    

    As you can see, the code to generate the custom objects is very similar to the array of hashtables you were passing to Select-Object -Property.

  • #19039
    Profile photo of VirGnar
    VirGnar
    Participant

    I tried the latter option (New-Object/[pscustomobject]) but the results were more or less the same as when I tried to create a custom object earlier as mentioned in my first reply. The only difference is that now it's repeated for each query:

    Group                                  User                                   Computer                              
    -----                                  ----                                   --------                              
    {Administrators, Administrators, Ad... {Administrator, Admin1, Superus... {Server1, Server1, Server1, S...
    {Administrators, Administrators, Ad... {Administrator, Admin1, Superus... {Server1, Server1, Server1, S...
    {Administrators, Administrators, Ad... {Administrator, Admin1, Superus... {Server1, Server1, Server1, S...
    {Administrators, Administrators, Ad... {Administrator, Admin1, Superus... {Server1, Server1, Server1, S...
    {Administrators, Administrators, Ad... {Administrator, Admin1, Superus... {Server1, Server1, Server1, S...
    {Administrators, Administrators, Ad... {Administrator, Admin1, Superus... {Server1, Server1, Server1, S...
    {Users, Users, Users, Users...}        {INTERACTIVE, Authenticated Users, ... {Server1, Server1, Server1, S...
    {Users, Users, Users, Users...}        {INTERACTIVE, Authenticated Users, ... {Server1, Server1, Server1, S...
    {Users, Users, Users, Users...}        {INTERACTIVE, Authenticated Users, ... {Server1, Server1, Server1, S...
    {Users, Users, Users, Users...}        {INTERACTIVE, Authenticated Users, ... {Server1, Server1, Server1, S...
    {Users, Users, Users, Users...}        {INTERACTIVE, Authenticated Users, ... {Server1, Server1, Server1, S...
    {Users, Users, Users, Users...}        {INTERACTIVE, Authenticated Users, ... {Server1, Server1, Server1, S...
    {Users, Users, Users, Users...}        {INTERACTIVE, Authenticated Users, ... {Server1, Server1, Server1, S...
    {Users, Users, Users, Users...}        {INTERACTIVE, Authenticated Users, ... {Server1, Server1, Server1, S...
    {Administrators, Administrators, Ad... {Administrator, Admin2, A... {Server2, Server2, S......
    {Administrators, Administrators, Ad... {Administrator, Admin2, A... {Server2, Server2, S......
    {Administrators, Administrators, Ad... {Administrator, Admin2, A... {Server2, Server2, S......
    {Administrators, Administrators, Ad... {Administrator, Admin2, A... {Server2, Server2, S......
    {Administrators, Administrators, Ad... {Administrator, Admin2, A... {Server2, Server2, S......
    {Administrators, Administrators, Ad... {Administrator, Admin2, A... {Server2, Server2, S......
    {Users, Users, Users}                  {INTERACTIVE, Authenticated Users, ... {Server2, Server2, S......
    {Users, Users, Users}                  {INTERACTIVE, Authenticated Users, ... {Server2, Server2, S......
    {Users, Users, Users}                  {INTERACTIVE, Authenticated Users, ... {Server2, Server2, S......
    
    

    I made sure to sanitize my shell environment to make sure there weren't any leftover variables from previous runs. I feel like I'm so close but so far!

  • #19040
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    Is that from running the code I posted? The output you're seeing looks like you were using $cimInstance[b]s[/b] instead of $cimInstance inside the third nested loop.

  • #19070
    Profile photo of VirGnar
    VirGnar
    Participant

    Excellent, that was the case. Thanks!

You must be logged in to reply to this topic.