Formatting a powershell custom object

Welcome Forums General PowerShell Q&A Formatting a powershell custom object

This topic contains 26 replies, has 8 voices, and was last updated by

 
Participant
3 months ago.

  • Author
    Posts
  • #157272

    Participant
    Topics: 9
    Replies: 34
    Points: 144
    Rank: Participant

    I have a powershell custom object which shows the GPONames and the values of particular settings like below:

    Group Policy:                     Group                                             MemberOf

    GPO1                                    test\Group1                                  Builtin\BackupOperators

    GPO1                                    test\Group2                                   Builtin\RemoteDesktopUsers

    I want the GPO Name to display only once for multiple corresponding values of Group and Member Off columns like:

    Group Policy                  Group                               MemberOf

    GPO1                                test\Group1                     Builtin\BackupOperators

    test\Group2                     Builtin\RemoteDesktopUsers

    How can I achieve this output in my powershell script?

  • #157304

    Participant
    Topics: 3
    Replies: 269
    Points: 103
    Helping Hand
    Rank: Participant

    This is an example that might work for you.

    #test object
    $obj = @"
    GroupPolicy: Group MemberOf
    GPO1 test\Group1 Builtin\BackupOperators
    GPO1 test\Group2 Builtin\RemoteDesktopUsers
    GPO2 test\Group3 Builtin\BackupOperators
    "@
    
    # group objects by grouppolicy
    $group = $obj | ConvertFrom-Csv -Delimiter ' ' |
    Group-Object -Property GroupPolicy:
    
    # display properties of each group
    $group | ForEach-Object {
        [PSCustomObject]@{
            GroupPolicy = $_.Name
            Group=$_.group.group
            MemberOf=$_.group.MemberOf
        }
    } | Format-List
    
  • #157311

    Participant
    Topics: 9
    Replies: 34
    Points: 144
    Rank: Participant

    Thanks for the reply. It is not a static list which I can put inside the $obj = @"

    It is a long list, which I want to format in such a way so as to show the GPO Name only once.

  • #157316

    Senior Moderator
    Topics: 8
    Replies: 1009
    Points: 3,281
    Helping Hand
    Rank: Community Hero

    Try this, on top of my head,didn't test...

    $YourCustomObject | Sort-Object -Property 'GroupPolicy' | Foreach-Object -Process {
    
        If($Previous -eq $_.GroupPolicy) {
           $_.GroupPolicy = $Null
        }
        $Previous = $_.GroupPolicy
        $_
    }
    
  • #157356

    Participant
    Topics: 9
    Replies: 34
    Points: 144
    Rank: Participant

    Tried it. It does not work.

  • #157358

    Participant
    Topics: 9
    Replies: 34
    Points: 144
    Rank: Participant

    I want the output like below:

    Group Policy Name Groups Members Of
    GPO1 Lab\Group1 Builtin\administrators
    Lab\Goup2 Builtin\Backup Operators
  • #157361

    Participant
    Topics: 2
    Replies: 999
    Points: 1,946
    Helping Hand
    Rank: Community Hero

    You are generating a column/table -based object, so, yep that is going be repeated by design.
    You can do the layout below, meaning, header then the remaining column layout, where the GPO name is separate from the table.

    Group Policy Name : GPO1
    Groups Members Of
    Lab\Group1 Builtin\administrators
    Lab\Goup2 Builtin\Backup Operators

    I don't have a bunch of GPO's to work with, but using Get-Process, what I'm trying to say is this...

    Get-Process | Group-Object -Property ProcessName | %{
    "Processing $($PSItem.Name)"
    $PSItem | Select -ExpandProperty Group
    }
    
    
    # Results
    
    Processing aesm_service
    
    Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
    -------  ------    -----      -----     ------     --  -- -----------
        187      11     3364       9552       0.08   5480   0 aesm_service
    Processing ApplicationFrameHost
        537      31    26932      43048       7.70  14096   6 ApplicationFrameHost
    Processing AppVShNotify
        163       8     2004       7520       0.08  14732   0 AppVShNotify
    Processing armsvc
        330      17     3228      15660       0.28   5624   0 armsvc
    Processing audiodg
        237      19    10628      15604   9,892.06  14292   0 audiodg
    Processing backgroundTaskHost
        562      26     8012      33304       0.31   9748   6 backgroundTaskHost
        322      29    11996      31340       0.17   9984   6 backgroundTaskHost
        482      37    14212      35188       0.14  16748   6 backgroundTaskHost
    Processing browser_broker
        154       9     1792       8804       0.06  29584   6 browser_broker
    Processing BuildService
        236      20     4876      10960     110.86   3904   0 BuildService
    Processing Code
        215      14     6456      13024       0.05   8460   6 Code
        391      35    54352      81184       5.19  19420   6 Code
        486      75   175652     220948      94.61  35172   6 Code
        389      74   117940     135592      12.14  38148   6 Code
    ...
    
  • #157364

    Participant
    Topics: 9
    Replies: 34
    Points: 144
    Rank: Participant

    No basically I want a column wise/tabular wise output with the GPO name displayed only once. Later I want to export the output as csv and view it in excel. However I want the powershell script to handle the displaying of GPO name only once.

  • #157365

    Participant
    Topics: 1
    Replies: 1482
    Points: 2,372
    Helping Hand
    Rank: Community Hero

    However I want the powershell script to handle the displaying of GPO name only once.

    Why?

  • #157376

    Participant
    Topics: 9
    Replies: 34
    Points: 144
    Rank: Participant

    Because one GPO name is corresponding to multiple values for Group and Member Of Columns and just for completeness I want to display the GPOName only once. For example one server can have multiple disks, so I want the servername to appear only once for all disks on the server and export the output as csv.

  • #157380

    Participant
    Topics: 1
    Replies: 1482
    Points: 2,372
    Helping Hand
    Rank: Community Hero

    So you will have to do the parsing of that output yourself. And it actually breaks the data structure. You're not able to use this data for something else than showing it to human beeings I think.

  • #157382

    Participant
    Topics: 9
    Replies: 34
    Points: 144
    Rank: Participant

    how to do it from powershell?

  • #157421
    js

    Participant
    Topics: 24
    Replies: 671
    Points: 1,564
    Helping Hand
    Rank: Community Hero

    You can name your object an make a view in a .format.ps1xml file. Then you wouldn't have to parse it afterwards.

    https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_format.ps1xml?view=powershell-5.1

  • #157487

    Participant
    Topics: 9
    Replies: 34
    Points: 144
    Rank: Participant

    Okay so there is no other easier way to do this?

  • #157499

    Participant
    Topics: 2
    Replies: 466
    Points: 1,058
    Helping Hand
    Rank: Community Hero

    There's (sort of) a way to do this, but it's only usable for display formatting. It looks nice, but it won't let you process the objects any further in your code with any coherency as it just converts them to format data.

    $Groups | Format-Table -Property Group, MemberOf -GroupBy 'Group Policy'

    Assuming, of course, that those are the actual names of the properties you're looking to show. Adjust as needed if your actual object properties differ. 🙂

  • #157616

    Participant
    Topics: 9
    Replies: 34
    Points: 144
    Rank: Participant

    Tried this also, it does not give the output as I want it.

  • #157658

    Participant
    Topics: 1
    Replies: 1482
    Points: 2,372
    Helping Hand
    Rank: Community Hero

    So you will have to do the parsing of that output yourself. Use a loop, check if the property you want to dismiss is the same as the one one loop-run before and dismiss it.

  • #157803

    Participant
    Topics: 2
    Replies: 999
    Points: 1,946
    Helping Hand
    Rank: Community Hero

    What Joel /u/ta11ow gave you is basically the same as what I gave you.

    As for …

    Okay so there is no other easier way to do this?

    … nope.

    As for...

    Later I want to export the output as csv and view it in excel.

    You can use the default output, swing it into Excel then use Excel to clean it up, you can automate that as well if you so choose to.

  • #157880

    Participant
    Topics: 2
    Replies: 999
    Points: 1,946
    Helping Hand
    Rank: Community Hero

    As we said, off the top of our heads, there is no easy way to do this, but it just bothered me to the degree that I could not let it go.

    All that being said. I went back to my personal library of old file system code, and completely refactored an item from the past and got this thing to work for the look and feel you are after.

    It took several hours of experimentation to figure this out BTW, to dig thru my library, and find a suitable item to mess with. Yet, once I got it, is was an OMG moment and this reminded my why I never throw my code away, that I thought I'd not need again.

    The code, will create the report, populate it out to a pre-created CSV dynamically, once complete, it will then show it on screen, in Out-GridView as it will show in Excel. Based on your use case, no further manipulation would be required. All with no custom object required.

    So, is this use case doable, yes, and I just proved that. Was it easy, nope. Yet, most new attempt at some things are a challenge, in many cases and that was why I decided to tackle it. Just because. Once done though, just like when you see an amazing magic trick, once they show you how it's done, it's often dead simple.

    Yet in PowerShell, there is always a dozen different ways to do something, and I am sure others could make this more elegant. so, this is just my success at it. Take it for what it is worth. It took a while to whittle this all down to the smallest amount of code I could wire up.

    ### Nested tabular reporting
    
    Clear-Host
    
    # Remove old log and create Log
    Remove-Item -Path 'D:\Scripts\Log.csv' -Force -ErrorAction SilentlyContinue
    Start-Sleep -Seconds 2
    $Output = 'D:\Scripts\Log.csv'
    Add-Content $Output 'RootObject,DataKey,ChildObjects'
    
    
    # Populate dataset for each object - Change the input to suit your needs
    $DataSetArray_1 = Get-Process | 
    Sort-Object -Property ProcessName | 
    Select-Object -Property ProcessName -Unique
    
    $DataSetArray_2 = Get-Process |
    Sort-Object -Property ProcessName |  
    Select ProcessName, Id, SI
    
    # Retrieve the dataset from each Object path
    ForEach ($DataSet_1 in $DataSetArray_1)
    {
        # "Checking for matches for $($DataSet_1.ProcessName)"
    
        $DataCheck = 0
    
        # Write-Host "Getting $($DataSet_1.ProcessName)" -Foreground Gray
    
        ForEach ($DataSet_2 in $DataSetArray_2)
        {
    	# Write-Host "Validating against $($DataSet_2.ProcessName)" -Foreground Green
    
            #Compare DataSet
            If ($DataSet_1.ProcessName -eq $DataSet_2.ProcessName)
            {
                #Output results to csv
                If ($DataCheck -eq 0)
                {
                    $DataCheck = 1
                    $Text = "$($DataSet_1.ProcessName), $($DataSet_2.Id), $($DataSet_2.ProcessName)"
                    # Write-Host "Adding primary results content $Text" -Foreground Cyan
                    Add-Content $Output $Text
                }
                else
                {
                    $Text = ", $($DataSet_2.Id), $($DataSet_2.ProcessName)"
                    # Write-Host "Adding secondary results content $Text" -Foreground Yellow
                    Add-Content $Output $Text 
                }
            }
        }     
    }
    
    Import-Csv -Path 'D:\Scripts\Log.csv'              # Send to screen
    Import-Csv -Path 'D:\Scripts\Log.csv' | 
    Out-GridView -Title 'Nested processes report'      # Send to GUI
    Start-Process -FilePath Excel 'D:\Scripts\Log.csv' # Open in Excel
    
    # Results (truncated for this response)
    
    RootObject                                                     DataKey ChildObjects
    ----------                                                     ------- ------------
    ...
    browser_broker                                                 7796    browser_broker
    BuildService                                                   3904    BuildService
    conhost                                                        7248    conhost
                                                                   12800   conhost
                                                                   5256    conhost
    CoordService                                                   4700    CoordService
    csrss                                                          880     csrss
                                                                   38024   csrss
    ctfmon                                                         8008    ctfmon
    dasHost                                                        5052    dasHost
    ...
    
    

  • #157908

    Participant
    Topics: 1
    Replies: 1482
    Points: 2,372
    Helping Hand
    Rank: Community Hero

    O M G

    😉

  • #157986

    Participant
    Topics: 2
    Replies: 999
    Points: 1,946
    Helping Hand
    Rank: Community Hero

    ;-}
    Olaf... yeah... maybe I should have left that part off.
    ;-}

  • #158018

    Participant
    Topics: 6
    Replies: 653
    Points: 20
    Rank: Member

    Since a code sample was already given, seemed like a fun challenge to see if it can be done simpler

    #Sample Data
    $data = [pscustomobject]@{
                'Group Policy' = 'GPO1'
                'Group' = 'test\Group1'
                'MemberOf' = 'Builtin\BackupOperator'
            },
            [pscustomobject]@{
                'Group Policy' = 'GPO1'
                'Group' = 'test\Group2'
                'MemberOf' = 'Builtin\RemoteDesktopUsers'
            },
            [pscustomobject]@{
                'Group Policy' = 'GPO1'
                'Group' = 'test\Group3'
                'MemberOf' = 'Builtin\PowerUsers'
            },
            [pscustomobject]@{
                'Group Policy' = 'GPO2'
                'Group' = 'test\Group4'
                'MemberOf' = 'Builtin\BackupOperator'
            },
            [pscustomobject]@{
                'Group Policy' = 'GPO2'
                'Group' = 'test\Group5'
                'MemberOf' = 'Builtin\RemoteDesktopUsers'
            },
            [pscustomobject]@{
                'Group Policy' = 'GPO2'
                'Group' = 'test\Group6'
                'MemberOf' = 'Builtin\PowerUsers'
            }
    
    #Sample Code
    $data | ForEach-Object {
        If ($_.'Group Policy' -eq $groupfirstobject.'Group Policy') {
            $_.'Group Policy' = $null
            $_
        }
        Else {
            $groupfirstobject = $_
            $_
        }  
    }

    Of course you can then export to csv if you like

    #Sample Code
    $data | ForEach-Object {
        If ($_.'Group Policy' -eq $groupfirstobject.'Group Policy') {
            $_.'Group Policy' = $null
            $_
        }
        Else {
            $groupfirstobject = $_
            $_
        }
        
    } | Export-Csv d:\test\test.csv -NoTypeInformation

    Sample Results

    Group Policy Group       MemberOf                  
    ------------ -----       --------                  
    GPO1         test\Group1 Builtin\BackupOperator    
                 test\Group2 Builtin\RemoteDesktopUsers
                 test\Group3 Builtin\PowerUsers        
    GPO2         test\Group4 Builtin\BackupOperator    
                 test\Group5 Builtin\RemoteDesktopUsers
                 test\Group6 Builtin\PowerUsers

     

  • #158180

    Participant
    Topics: 9
    Replies: 34
    Points: 144
    Rank: Participant

    Thanks Curtis. I will try it out today and let you know if it works or not. However I hope I do not need to populate the $data variable manually as done in the above code as there are 100's of GPO's. I put two GPO's just as an example.

    Also would you explain a bit of the logic behind the below code:

    $data | ForEach-Object {
    If ($_.'Group Policy' -eq $groupfirstobject.'Group Policy') {
    $_.'Group Policy' = $null
    $_
    }
    Else {
    $groupfirstobject = $_
    $_
    }

    } | Export-Csv d:\test\test.csv -NoTypeInformation

     

    Like $groupfirstobject.'Group Policy' what is this going to do?

  • #158195

    Participant
    Topics: 1
    Replies: 1482
    Points: 2,372
    Helping Hand
    Rank: Community Hero

    Please start to format the code as code here in the forum using the code tag button (pre) on the post editor (second to last button).
    Thanks

  • #158198

    Participant
    Topics: 2
    Replies: 999
    Points: 1,946
    Helping Hand
    Rank: Community Hero

    I see others have joined the fray before I got back to this.
    After finally getting back to a DC, I see Chris, got here before I did, but he is using static entries.
    I was reworking mine, to keep it dynamic. So …

    $GpoData = @()
    
    Get-GPO -All | 
    ForEach {
    $GpoData += [pscustomobject]@{
                'GroupPolicy'  = $PSItem.DisplayName
                'Group'        = $PSItem.Owner
                'MemberOf'     = (Get-ADGroupMember -Identity ($($PSItem.Owner) -replace '.*\\'))
            }
    }
    
    $CurrentPolicy = $null
    
    ForEach ($Policy in $GpoData)
    {
        # $Policy.GroupPolicy
        # $Policy.MemberOf.SamAccountName
    
        ForEach($Member in $Policy.MemberOf.SamAccountName)
        {
            If($Policy.GroupPolicy -ne $CurrentPolicy)
            {
                [pscustomobject]@{
                    'GroupPolicy' = $Policy.GroupPolicy
                    'Group'       = $Policy.Group
                    'Member'      = $Member
                }
            }
            Else
            {
                [pscustomobject]@{
                    'GroupPolicy' = ''
                    'Group'       = $Policy.Group
                    'Member'      = $Member
                }        
            }
    
            $CurrentPolicy = $Policy.GroupPolicy
        }
    }
    
    # Results
    
    GroupPolicy                           Group                   Member
    -----------                                             -----                   ------                  
    Default Domain Policy                 Contoso\Domain Admins  Administrator
                                          Contoso\Domain Admins  adcsadmin
                                          Contoso\Domain Admins  adfsadmin
    ...
    Default Domain Controllers Policy     Contoso\Domain Admins  Administrator
                                          Contoso\Domain Admins  adcsadmin
                                          Contoso\Domain Admins  adfsadmin
    ...
    
  • #158279

    Participant
    Topics: 6
    Replies: 653
    Points: 20
    Rank: Member

    Thanks Curtis. I will try it out today and let you know if it works or not. However I hope I do not need to populate the $data variable manually as done in the above code as there are 100's of GPO's. I put two GPO's just as an example.

    Also would you explain a bit of the logic behind the below code:

    $data | ForEach-Object {

    If ($_.'Group Policy' -eq $groupfirstobject.'Group Policy') {

    $_.'Group Policy' = $null

    $_

    }

    Else {

    $groupfirstobject = $_

    $_

    }

    } | Export-Csv d:\test\test.csv -NoTypeInformation

    Like $groupfirstobject.'Group Policy' what is this going to do?

    You never actually provided information on how you were getting your data.  IE via Spreadsheet, a commandlet, something else?  So I just made custom PSObject that represented the data in the format you provided.  You can adjust it to be whatever datasource you want.

    What the code is doing is very simple:

    1. It Pipes the dataset to ForEach-Object
    2. It look at the current object and sees if the "Group Policy" property on that object matches the "Group Policy" property on the object in variable $groupfirstobject (When the first object comes down the pipeline $groupfirstobject will be null so it will not match)
    3. If it does not match, it sets $groupfirstobject to be the same as the current object in the ForEach-Object and then sends the current object to the pipeline
    4. The next object that comes down the will be checked against $groupfirstobject to see if their "Group Policy" properties match just like before except this time $groupfirstobject contains the first object that came down the pipeline
    5. If it matches, then it just sets the "Group Policy" property on the current object to $null, basically blanking out this property so it is seen as blank when output to the shell or CSV, then send the updated object with a blank "Group Policy" property to the pipeline
    6. If it does not match, then this is a new Group Policy group, so it updates the $groupfirstobject variable to be this current object and repeats the process outlined above
  • #158312

    Participant
    Topics: 9
    Replies: 34
    Points: 144
    Rank: Participant

    Thanks Curtis for the explanation. I can understand it now and I tried the code it works fine also.

The topic ‘Formatting a powershell custom object’ is closed to new replies.