Creating PSObject within itself

I'm trying to loop through our mailbox servers and record some data on usage.
What I'm aiming for is an object that contains the Server name and "Volume Information".
Within that Volume Info, I want to have Capacity, Freespace and "Database Usage"
Within that Database Usage, I want to have reference to each Database, the number of mailboxes on that database and how much space is used by each.
Acquiring the data isn't the problem, I have that down.  It's the combination of all those objects that I'm struggling with.
I'd like to be able to look at
$server[0].Volume.Capacity
$server[0].Volume.FreeSpace
$server[0].Volume.DatabaseUsage
$server[0].Volume.DatabaseUsage[0].DatabaseName
$server[0].Volume.DatabaseUsage[0].MailboxCount
$server[0].Volume.DatabaseUsage[0].TotalSize

How can I create these custom 'child' objects so that I can reference those values as outlined above?
LVL 67
sirbountyAsked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

LearnctxEngineerCommented:
An object is basically just a key value pair (hash). So for every property you want to contain an object as a value, just set the value as a new hash/dictionary object. I'm only including the properties you have specified in your question. But of course you might want to add some more (server name for example and so on).

$Server = New-Object PSObject -Property @{
	Volume = @{
		Freespace = $null
		DatabaseUsage = @{
			DatabaseName = $null
			MailboxCount = $null
			TotalSize = $null
		}
	}
}

Open in new window

0
Chris DentPowerShell DeveloperCommented:
Good morning sirbounty,

I'm a little confused about what you'd like to see. There are a number of approaches that might serve.

One is to flatten the objects down completely, and that could be used to present the view you see above at the expense of increasing duplication in the data-set. This changes your current data structure though.

Another approach is to present alternate representations of the children in a nested data set. For instance, you could present DatabaseName, MailboxCount and TotalSize in a string by overriding the ToString method on the custom object you've got there. Get-Acl does something like this (see the AccessToString property).

Chris
0
Chris DentPowerShell DeveloperCommented:
Learnctx,

While New-Object PSObject can construct an object from a HashTable, the properties of the resulting object are not themselves a HashTable.

The structure built above cannot be used with the majority of formatting, selection and export CmdLets simply because the members you've added aren't turned into properties on an object, they remain as a HashTable.

See:
$Server.GetType()
$Server.Volume.GetType()

Open in new window

You can still use the HashTables, but you'd need a PSObject at each level.
$Server = New-Object PSObject -Property @{
	Volume = (New-Object PSObject -Property @{
		Freespace = $null
		DatabaseUsage = (New-Object PSObject -Property @{
			DatabaseName = $null
			MailboxCount = $null
			TotalSize = $null
		})
	})
}

Open in new window


Chris
0
Make Network Traffic Fast and Furious with SD-WAN

Software-defined WAN (SD-WAN) is a technology that determines the most effective way to route traffic to and from datacenter sites. Register for the webinar today to learn how your business can benefit from SD-WAN!

LearnctxEngineerCommented:
Right you are Chris. I hadn't thought it though 100%; was trying to bend my head around the question :)
The other issue with doing it is of course it can all easily be overwritten.

$Server.Volume.DatabaseUsage = "x"

Will just overwrite the object into a string.
0
Qlemo"Batchelor", Developer and EE Topic AdvisorCommented:
... and preventing that would require to create several .NET classes with Add-Type compiling  some .NET code, so you have strong typing for all properties available.
0
Chris DentPowerShell DeveloperCommented:
Indeed, the flexible "typing" is very useful, but you can't use access modifiers (get / set) unless you either create a class in a dynamic assembly or head back to Add-Type.

Anyway, I'm deviating from the topic at hand :)
0
sirbountyAuthor Commented:
If it makes it overly complex, then I don't want to pursue it.  I can obtain all of the above information, so I don't need specific coding solutions anyway.
This was more for a learning experience for me.  I've created some child objects for my custom objects before, but always get stuck with overwriting existing 'grandchild' objects.
My end goal is to create a table containing each of our servers, each of their (edb) volumes, and for each volume show the size/free space, along with a total of the mailboxes, and some other related data (quota usage, etc) for each of those databases on that volume.
0
Chris DentPowerShell DeveloperCommented:
One way to handle Overwrite is to instantiate the child as an array in the first place, then add to it. e.g.
$Family = New-Object PSObject -Property ([Ordered]@{
  Surname = "James"
  Children = @("Jon")
})
$Family.Children += "Tim"
$Family.Children += "Debbie"

Open in new window

If the data held in there is complex you might consider creating a return value you can drill into. For example:
$Family = New-Object PSObject -Property ([Ordered]@{
  Surname = "James"
  Children = @()
})
$Child = New-Object PSObject -Property ([Ordered]@{
  Name = "Jon"
  Age = 12
  Gender = "Male" 
})
$Child | Add-Member ToString -MemberType ScriptMethod -Force -Value {
  return "$($this.Name) ($($this.Age))"
}
$Family.Children += $Child

Open in new window

Using this approach you can have a summarised view by exporting the parent. Might need something to present the Children array as a string for that, we loop back to what Get-Acl does there. Detailed information about Children if you drill down into the Family object.

Chris
0
sirbountyAuthor Commented:
I think that follows closely with what I've been developing.
I don't think I'm quite 100% there yet though.
Here's what I have, and obviously it will take a bit to run, so I'd like a second set of eyes to see where I might be overlooking something:

$EnvironmentResults = New-Object -TypeName PSObject

foreach ($MailboxServer in Get-MailboxServer) {
    $ServerDetails = New-Object -TypeName PSObject
    $volumes = Get-WmiObject -Class win32_volume -ComputerName $MailboxServer -Property caption,name,capacity,freespace,label | Where-Object {$_.name -gt 'E:\'}
    foreach ($volume in $volumes) {
        $VolumeDetails = New-Object -TypeName PSObject
        $VolumeDetails | Add-Member -MemberType NoteProperty -Name Volume -Value $volume.name
        $VolumeDetails | Add-Member -MemberType NoteProperty -Name Capacity -Value ($volume.capacity/1mb -as [int])
        $VolumeDetails | Add-Member -MemberType NoteProperty -Name FreeSpace -Value ($volume.freespace/1mb -as [int])
        $DatabaseUsage = @()
        $databases = Get-MailboxDatabase -Server $MailboxServer | Where-Object {$_.edbfilepath -like "$($volume.name)*"}
        foreach ($database in $databases) {
            $mailboxes = Get-Mailbox -Database $database
            $ManagedMailboxes = $mailboxes | Where-Object {$_.usedatabasequotadefaults -eq $true}
            $UnManagedMailboxes = $mailboxes | Where-Object {$_.usedatabasequotadefaults -eq $false}
            $Usage = New-Object -TypeName PSObject 
            $Usage | Add-Member -MemberType NoteProperty -Name Database -Value $database.Name
            $Usage | Add-Member -MemberType NoteProperty -Name Mailboxes -Value $mailboxes.count
            $Usage | Add-Member -MemberType NoteProperty -Name ManagedMailboxes -Value $ManagedMailboxes.count
            $Usage | Add-Member -MemberType NoteProperty -Name UnManagedMailboxes -Value $UnManagedMailboxes.count
            $databaseIWQ = $database.issuewarningquota.value.tomb()
            $databasePSR = $database.prohibitsendreceivequota.value.tomb()
            $DatabaseQuotaTotal += ($databasePSR * $ManagedMailboxes.count)
            $DatabaseQuotaTotal += ($unManagedMailboxes | Where-Object {$_.prohibitsendreceivequota -notlike 'unlimited'} | Select-Object @{label='PSRQ';expression={$_.prohibitsendreceivequota.value.tomb()}} | Select-Object -ExpandProperty PSRQ | Measure-Object -sum).sum
            foreach ($mailbox in $mailboxes) {
                $DatabaseQuotaUsed += Get-MailboxStatistics $mailbox | Select-Object @{label='Used';e={$_.totalitemsize.value.tomb()}} | Select-Object -ExpandProperty Used
            }
            $Usage | add-member -MemberType NoteProperty -Name QuotaPromised -Value $DatabaseQuotaTotal
            $Usage | Add-Member -MemberType NoteProperty -Name QuotaUsed -Value $DatabaseQuotaUsed
            $DatabaseUsage += @($Usage)
        }
        $DatabaseDetails = New-Object -TypeName PSObject
        $DatabaseDetails | add-member -MemberType NoteProperty -Name Databases -Value $DatabaseUsage
        $VolumeUsage += @($DatabaseDetails)
        $ServerUsage += @($VolumeUsage)
        $ResultData += @($ServerUsage)
    }
    $VolumeDetails | add-member -MemberType NoteProperty -name DatabaseDetails -Value $ServerUsage
}
$EnvironmentResults | Add-Member -MemberType NoteProperty -Name ServerUsage -Value $ResultData

Open in new window

0
Chris DentPowerShell DeveloperCommented:
Oh yes, very similar.

The problem with this kind of structure is that it's difficult to access property members on the parent from the child. You end up creating a new version of the child to add in the information you want from the parent.

Looking at the example above (simple / hackable) that would mean doing something like this:
$Family.Children | Select-Object Name, @{n='Surname';e={ $Family.Surname }}

Open in new window

In some cases that's just fine, you may rarely care about the information on the parent. Or you may not want to consume all the resources involved in flattening it, or there may be so may permutations and combinations that duplication becomes truly excessive.

In short, there's nothing wrong with the structure above (why would there be?). Whether or not it's suitable depends entirely on what you want to do with it once you've collected it. For example, how you expect it to behave when you throw it into an output formatter.

Chris
0
sirbountyAuthor Commented:
I'm already over-complicating this project.
Realistically, I can just loop through the data that I'm after and convert it to an html table, which is the end goal.
This was just one of those - 'I got a bit bored along the way, so I thought I'd play' projects. :^)
Though I was hoping afterwards to be able to perform something like
foreach ($server in $myresults) {
  $html += '<tr><td>{0}</td>'  -f $server.name
  foreach ($volume in $server.volumes) {
    $html += '<td>{0}</td><td>{1}</td><td>{2}</td>' -f $volume.label, $volume.capacity, $volume.freespace
    foreach ($database in $volume.databases) {
      $html += '<td>{0}</td><td>{1}</td><td>{2}</td>' -f $database.name, $database.mailboxcount, $database.quotaTotal


Though I realize there would be significant redundancy, I figured I could create a summary row displaying each volume's total usage by the database/mailbox amounts used/allocated.

Too much?  I dunno.  I'm sure it's simpler to just build the html data along the way and not worry about these custom objects...
0
Chris DentPowerShell DeveloperCommented:
Perhaps, but half the fun of scripting is doing it because you can or want to, isn't it? :)

As for building the HTML, have you considered ConvertTo-Html? I normally build using that feeding in only a style sheet. Individual tables are built using "ConvertTo-Html -Fragment". Saves a lot of messing around with table tags.

Doing that lets you split formatting out from the body of the logic. And it makes use of all those custom objects you've been hanging together :)

Chris
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
sirbountyAuthor Commented:
I have not.  I'm still in the build-as-I-go stage.
I'll look into that further.  Though I welcome any examples alongside my usage above.
Thanks!
0
Chris DentPowerShell DeveloperCommented:
Such a busy night. Two small children do that it seems.

Anyway, I'm expanding on  the Family example simply because it's easier to hack a bit (sleepy because of the issue above). I've moved the object construction into functions to make the purpose a little more obvious. I've added a few different output format helpers on each of the objects.

Then I've added how I would (and normally do) handle HTML formatting.

I don't normally hard-code the HTML Head, that's normally read from a resource folder I keep such nonsense in. That way I can have consistent style across all scripts, and only ever need to update it once.

Anyway, without further to do:
function New-Family {
  [CmdLetBinding()]
  param(
    [Parameter(Mandatory = $true)]
    [ValidateNotNullOrEmpty()]
    [String]$Surname
  )

  $Family = New-Object PSObject -Property ([Ordered]@{
    Surname  = $Surname
    Children = @()
  })
  $Family | Add-Member ChildrenAsString -MemberType ScriptProperty -Value {
    return (($Family.Children | ForEach-Object { $_.ToString() }) -join "`n")
  }
  return $Family
}

function New-Child {
  [CmdLetBinding()]
  param(
    [Parameter(Mandatory = $true)]
    [ValidateNotNullOrEmpty()]
    [String]$Name,

    [ValidateRange(0, 18)]
    [Byte]$Age = 0,

    [Parameter(Mandatory = $true)]
    [ValidateSet("Male", "Female")]
    [String]$Gender,

    [String]$FavouriteColour
  )

  $Child = New-Object PSObject -Property ([Ordered]@{
    Name            = $Name
    Age             = $Age
    Gender          = $Gender
    FavouriteColour = $FavouriteColour
  })
  $Child | Add-Member ToString -MemberType ScriptMethod -Force -Value {
    return "$($this.Name) ($($this.Age))"
  }
  return $Child
}

$Family = New-Family "James"
$Family.Children += New-Child Jon -Age 12 -Gender Male -FavouriteColour Red
$Family.Children += New-Child Rachel -Age 1 -Gender Female

# Output generation
$HtmlHead = "
<style type='text/css'>
    body {
      font-size: 10pt;
      font-family: sans-serif;
      padding: 0px; 
      margin: 0px; 
      overflow: auto;
    }

    table        { width: 100%; border-collapse: collapse }
    td, th       { color: black; padding: 2px; }
    th           { background-color: #C7C7C7; text-align: left }
    td           { background-color: #F7F7F7; text-align: left }
</style>"

$HtmlBody = @("<h1>The Family</h1>", "<h2>Summary</h2>")
$HtmlBody += $Family | Select-Object Surname, @{n='Children';e={ $_.ChildrenAsString }} | ConvertTo-Html -Fragment
$HtmlBody += "<h2>Children</h2>"
$HtmlBody += $Family.Children | ConvertTo-Html -Fragment

$HtmlDocument = ConvertTo-Html -Body ($HtmlBody | Out-String) -Head $HtmlHead | Out-String
# Drop the empty table created by the last call to ConvertTo-Html
$HtmlDocument = $HtmlDocument -replace '<table>\r?\n</table>'
$HtmlDocument | Out-File TheFamily.html

Open in new window

And just a quick note on Out-String. ConvertTo-Html gives you a string array, this simply flattens it into a simple string adding line breaks instead of different array items. It's important if you intend to use Send-MailMessage with the result and it helps drop the empty table.

Chris
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Powershell

From novice to tech pro — start learning today.