Formatting a powershell custom object

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?

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

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.

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
    $_
}

Tried it. It does not work.

I want the output like below:

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

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
...

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.

Why?

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.

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.

how to do it from powershell?

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

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

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. :slight_smile:

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

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.

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.

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
...

O M G

:wink: