Is this possible? if so some pointers would be much appreciated - thank you

Hi

New to PowerShell Universal…

Is it possible to show results from multiple computers in one table on a dashboard? for example I would like to get disk space usage from multiple servers / computers across my AD environment and then show that information in a single table, with server name in one column, drive letters across the top, and free space in cells for each drive.

Furthermore is it then possible to highlight in some way certain cells if value is below a specified amount? to make it stand out, and perhaps also show an alert as well?

I would also like to do similar to show state of specific windows services on serval servers which are prone to stopping without warning so we can have a single place to easily view the current state of these services, again potentially with clear highlighting / alerting if stopped and perhaps a button to start the service from the dashboard.

As I say I am new to Powershell Universal so still learning so would appreciate to know if this is possible and perhaps some pointers from more experienced users on how to at least start implementing some of this, I learn best from seeing examples of how stuff works so I can then build on it from there.

Thank you in advance for your time :slight_smile:

Product: PowerShell Universal
Version: 3.5.5

Sure is. It’s all just Powershell. You’ll have to query that information within your dashboard though.

Absolutely. You’ll want to use the -render parameter on New-UDTableColumn. -Render accepts a script block, so from there you can do an if statement or switch.

Same as the first point.

I recommend using New-UDEndpoint to query for that information.

The reason to use the New-UDEndpoint is that you can automatically refresh the data on a schedule and it means you aren’t running it in a page, meaning it won’t cause the page to load slowly as it reaches out and gets the data in real time.

Thank you very much indeed for your response, if not being too cheeky could someone show me a simple code example of perhaps pulling results from one computer, nothing too detailed just to give me some guidance, as I said I learn best from seeing examples, and this is all very new to me, then I can get my head around how it works and expand it further. Thanks again

Could someone please give me some pointers here.

The below code is a real basic example of what I would use previously to get information from a list of servers in normal PowerShell.

$servers = @("SERVER1", "SERVER2")

$DiskReport = ForEach ($Server in $Servers) {

     Get-WmiObject -Class Win32_LogicalDisk -Filter "Drivetype=3" -ComputerName $Server |
     Select-Object -Property PSComputerName, DeviceID, VolumeName, @{Label='FreeSpace (Gb)'; 
     expression={($_.FreeSpace/1GB).ToString('F2')}}|ft


}

Just sending this output to New-UDTable doesn’t work, so obviously not as simple as that.

How would I get the information from $DiskReport into a table in PowerShell Universal with three columns - computer name, drive letter / volume name, free space

How could I improve the way I store / retrieve the information so that instead of multiple rows per server I could have just one row per server with multiple columns e.g. server name in column1, then first drive letter / volume name goes in to column 2, free space for that drive in column 3, second drive letter / volume name goes in to column 3, free space for that drive in column 4, etc - as think that might look cleaner when showing lots of servers which all have multiple drive letters?

SERVER 1 C: 56Gb D: 32Gb E: 26Gb

Thoughts / comments / input greatly appreciated from those more experienced in using this who have got existing layouts perhaps which work for showing multiple server / drive space information perhaps?

Thanks again

EDIT: further to last post I have made some progress it seems :slight_smile:

Below is at least getting me some output to a table, with one server drive letter per line, and the size converted from bytes to GB/MB etc

function ConvertTo-ByteString {
    param(
        [Parameter(ValueFromPipeline = $true)]
        $byteCount
    )

    Process {
        $suf = @( "B", "KB", "MB", "GB", "TB", "PB", "EB" )
        if ($byteCount -eq 0)
        {
            return "0" + $suf[0];
        }
            
        $bytes = [Math]::Abs($byteCount);
        $place = [Convert]::ToInt32([Math]::Floor([Math]::Log($bytes, 1024)))
        $num = [Math]::Round($bytes / [Math]::Pow(1024, $place), 1)
        return ([Math]::Sign($byteCount) * $num).ToString() + $suf[$place]
    }

}

New-UDDashboard -Title 'PowerShell Universal' -Content {
    $servers = @("SERVER1", "SERVER2")


$DiskReport = ForEach ($Server in $Servers) {

Get-CimInstance -Class Win32_LogicalDisk -Filter "Drivetype=3" -ComputerName $Server 


}

$Columns = @(
    New-UDTableColumn -Property PSComputerName -Title "Computer" 
    New-UDTableColumn -Property DeviceID -Title "Drive Letter" 
    New-UDTableColumn -Property VolumeName -Title "Volume Name" 
    New-UDTableColumn -Property FreeSpace -Title "Free Space" -Render {ConvertTo-ByteString ($EventData.FreeSpace)}
)


New-UDTable -Columns $Columns -Data $DiskReport


}

So next steps

  1. How could I improve the way I store / retrieve the information so that instead of multiple rows per server I could have just one row per server with multiple columns e.g. server name in column1, then first drive letter / volume name goes in to column 2, free space for that drive in column 3, second drive letter / volume name goes in to column 3, free space for that drive in column 4, etc - as think that might look cleaner when showing lots of servers which all have multiple drive letters?

  2. How can I highlight a value (or row) if value below a certain figure e.g. to signify a warning if space is low for example

I can then look into the great advice in first post about using New-UDEndpoint to do the checks on a schedule etc

Thanks, all a learning curve!

Okay, more progress made, have now worked out how to colour cell value depending on result, and also how to use New-UDEndpoint to check results every x minutes and refresh the content using New-UDDynamic

The one part I can’t work out and how I would prefer my table to look, especially as I start adding more servers is Q1 above, if anyone can give me some suggestions please, quick mockup of table layout to visualize what I mean below.

image

Updated code for what I have working so far below:-

function ConvertTo-ByteString {
    param(
        [Parameter(ValueFromPipeline = $true)]
        $byteCount
    )

    Process {
        $suf = @( "B", "KB", "MB", "GB", "TB", "PB", "EB" )
        if ($byteCount -eq 0)
        {
            return "0" + $suf[0];
        }
            
        $bytes = [Math]::Abs($byteCount);
        $place = [Convert]::ToInt32([Math]::Floor([Math]::Log($bytes, 1024)))
        $num = [Math]::Round($bytes / [Math]::Pow(1024, $place), 1)
        return ([Math]::Sign($byteCount) * $num).ToString() + $suf[$place]
    }

}

$EndpointSchedule = New-UDEndpointSchedule -Every 10 -Minute
New-UDEndpoint -Schedule $EndpointSchedule -Endpoint {
   
 $servers = @("SERVER01","SERVER02", "SERVER03")


$Cache:DiskReport = ForEach ($Server in $Servers) {

Get-CimInstance -Class Win32_LogicalDisk -Filter "Drivetype=3" -ComputerName $Server | Select-Object -Property PSComputerName, DeviceID, VolumeName, FreeSpace


}
   
} | Out-Null


New-UDDashboard -Title 'PowerShell Universal' -Content {
   
$Columns = @(
    New-UDTableColumn -Property PSComputerName -Title "Computer" 
    New-UDTableColumn -Property Drive -Title "Drive" -Render {$EventData.DeviceID + " (" + $EventData.VolumeName +")"}
    New-UDTableColumn -Property FreeSpace -Title "Free Space" -Render {
        
        if ($EventData.Freespace -gt 90513215489) {New-UDElement -Tag 'div' -Attributes @{style = @{ 'backgroundColor' = 'green'; 'color' = 'white'}} -Content {ConvertTo-ByteString ($EventData.FreeSpace)}}
        else{New-UDElement -Tag 'div' -Attributes @{style = @{ 'backgroundColor' = 'red'; 'color' = 'white'}} -Content {ConvertTo-ByteString ($EventData.FreeSpace)}}
                
        }
)

New-UDDynamic -Content {

New-UDTable -Columns $Columns -Data $Cache:DiskReport


$LastChecked = Get-Date
New-UDTypography -Text ("Last Updated: " + $LastChecked)

} -AutoRefresh -AutoRefreshInterval 600
}```

Because there isn’t a consistent number of drives per machine it might be kind of wonky to setup columns for all drives.

One thing that might work better is two columns Computer and Drive and then in the -Render for the drive, you could include all the drives free space.

New-UDDashboard -Content {
    $Computers = 1..10 | ForEach-Object {
        $Computer = @{
            Computer = "Computer$_"
            Drives   = @()
        }

        1..5 | % { 
            $Computer.Drives += [PSCustomObject]@{ Letter = (65..90 | % { [char]$_ } | Get-Random); FreeSpace = (Get-Random -Minimum 1 -Max 500) }
        }

        $Computer
    }

    New-UDTable -Data $Computers -Columns @(
        New-UDTableColumn -Property Computer -Title "Computer"
        New-UDTableColumn -Property Drives -Title "Drives" -Render {
            New-UDStack -Direction row -Children {
                $EventData.Drives | Sort-Object -Property Letter | ForEach-Object {
                    New-UDElement -Tag Div -Content {
                        if ($_.FreeSpace -lt 100) {
                            New-UDAlert -Text "$($_.Letter): | $($_.FreeSpace) GB"  -Severity error
                        }
                        else {
                            New-UDAlert -Text "$($_.Letter): | $($_.FreeSpace) GB"
                        }
                    } -Attributes @{
                        style = @{ width = "200px"}
                    }
  
                }
            } -FullWidth -Spacing 5
        } 
    ) -OnRowExpand {
        New-UDTable -Data $EventData.Drives 
    }
}

I also included an OnRowExpand if you wanted to include some additional info on each computer.

@Adam Dang, you beat me to it…

@wingers another thing you can do to improve this is Get-CimInstance supports multiple computers, so you don’t actually need the Foreach loop.

Additionally, Get-CimInstance supports -Property. This makes sure the computer only returns data you want. You still need to do Select-Object, as it will return a bunch of blank properties on you. But this just optimizes the process in consideration to network and memory resources.

Get-CimInstance -Class Win32_LogicalDisk -Filter "Drivetype=3" -ComputerName $Servers -Property DeviceID, VolumeName, FreeSpace | Select-Object -Property PSComputerName, DeviceID, VolumeName, FreeSpace

In my test case, with 3 computers it saved around 1 second. It doesn’t include a throttle command, so your mileage may vary. But it should be possible to do these things “in blocks” to prevent querying too many systems at once.

For example

$ServerCount = $Servers.count
$Throttle = 50
$x = [Math]::Ceiling($ServerCount/$Throttle)
$i = 0
$Cache:DiskReport = 1..$x | Foreach {
    $Batch = $Servers | Select -First $Throttle -Skip $i
    Get-CimInstance -Class Win32_LogicalDisk -Filter "Drivetype=3" -ComputerName $Batch -Property DeviceID, VolumeName, FreeSpace | Select-Object -Property PSComputerName, DeviceID, VolumeName, FreeSpace
    $i = $i + 50
}
1 Like

@Jori - thank you very much, I have made the changes you suggested, and when running against a long list of servers it does indeed make quite a bit of difference - so thanks

@adam - Thank you, I really like the layout you suggest it looks so much cleaner to show multiple drives, but I am having issues converting the logic in your example to work with my code. I can get it to show the values but it is still showing one drive per line rather than grouping them per computer, I think it is because my source data is formatted differently than what your example is and I can’t work out how to rework the table structure to compensate for this - below is what I have done so far. Any help would be appreciated. I also need to reimplement the conversion of the displayed value back to GB/TB etc using so it reads more easily, currently just shows as bytes which is messy.

function ConvertTo-ByteString {
    param(
        [Parameter(ValueFromPipeline = $true)]
        $byteCount
    )

    Process {
        $suf = @( "B", "KB", "MB", "GB", "TB", "PB", "EB" )
        if ($byteCount -eq 0)
        {
            return "0" + $suf[0];
        }
            
        $bytes = [Math]::Abs($byteCount);
        $place = [Convert]::ToInt32([Math]::Floor([Math]::Log($bytes, 1024)))
        $num = [Math]::Round($bytes / [Math]::Pow(1024, $place), 1)
        return ([Math]::Sign($byteCount) * $num).ToString() + $suf[$place]
    }

}

$EndpointSchedule = New-UDEndpointSchedule -Every 10 -Minute
New-UDEndpoint -Schedule $EndpointSchedule -Endpoint {
   
 $servers = @("SERVER01","SERVER02", "SERVER03")


$Cache:DiskReport = Get-CimInstance -Class Win32_LogicalDisk -Filter "Drivetype=3" -ComputerName $Servers -Property DeviceID, VolumeName, FreeSpace | Select-Object -Property PSComputerName, DeviceID, VolumeName, FreeSpace 

} | Out-Null

New-UDDashboard -Title 'PowerShell Universal' -Content {

New-UDDynamic -Content {

New-UDTable -Data $Cache:DiskReport -Columns @(

        New-UDTableColumn -Property PSComputerName -Title "Computer"

        New-UDTableColumn -Property Drives -Title "Drives" -Render {

            New-UDStack -Direction row -Children {

                $EventData | Sort-Object -Property Letter | ForEach-Object {

                    New-UDElement -Tag Div -Content {

                        if ($_.FreeSpace -lt 90513215489) {

                            New-UDAlert -Text "$($_.DeviceID) | $($_.FreeSpace) GB"  -Severity error

                        }

                        else {

                            New-UDAlert -Text "$($_.DeviceID) | $($_.FreeSpace) GB"

                        }

                    } -Attributes @{

                        style = @{ width = "200px"}

                    }

  

                }

            } -FullWidth -Spacing 5

        } 

    ) -OnRowExpand {

        New-UDTable -Data $EventData.Drives 

    }

$LastChecked = Get-Date
New-UDTypography -Text ("Last Updated: " + $LastChecked)

} -AutoRefresh -AutoRefreshInterval 600


}

@adam - okay I have spent hours on this now and I am going round in circles so would really appreciate your input, please :slight_smile:

As I said above I love your idea for the layout, looks so much cleaner, but I can’t get it to work with the data I am retrieving as below:-

$EndpointSchedule = New-UDEndpointSchedule -Every 10 -Minute
New-UDEndpoint -Schedule $EndpointSchedule -Endpoint {
   
 $servers = @("SERVER01","SERVER02", "SERVER03")


$Cache:DiskReport = Get-CimInstance -Class Win32_LogicalDisk -Filter "Drivetype=3" -ComputerName $Servers -Property DeviceID, VolumeName, FreeSpace | Select-Object -Property PSComputerName, DeviceID, VolumeName, FreeSpace 

} | Out-Null

N.B. You can run command on a single computer with multiple drives if no access to network with multiple computers to get output for testing - just using SystemName property instead of PSComputerName property e.g.

$DiskReport = Get-CimInstance -Class Win32_LogicalDisk -Filter "Drivetype=3" -Property SystemName, DeviceID, VolumeName, FreeSpace | Select-Object -Property SystemName, DeviceID, VolumeName, FreeSpace

I think you just need to group your data by computer name.

$Cache:DiskReport = Get-CimInstance -Class Win32_LogicalDisk -Filter "Drivetype=3" -ComputerName $Servers -Property DeviceID, VolumeName, FreeSpace | Select-Object -Property PSComputerName, DeviceID, VolumeName, FreeSpace | Group-Object -Property PSComputerName

Then, in your table columns, you can use:

        New-UDTableColumn -Property Name -Title "Computer"

        New-UDTableColumn -Property Group -Title "Drives" -Render {

            New-UDStack -Direction row -Children {

                $EventData.Group | Sort-Object -Property Letter

#...

@adam

Thank you so much, that was the solution I needed and it is now working fine. Can now play about with layout and tweak it to my requirements.

Can’t believe how much fun I am having playing about with this!

A related question please - a couple of servers have a lot of drives is there anyway to have them wrap to next line rather than showing a scroll bar? - see image below, last server has two more drives appearing off screen, would be nice if they appear below instead?

Also is it possible to fix width of first column (Computer) so that the text doesn’t wrap to two lines? - tried -Width but it just seems to ignore it

I think if you get rid of the scrolling the -Width will work. Try something like this.

New-UDDashboard -Content {
    $Computers = 1..10 | ForEach-Object {
        $Computer = @{
            Computer = "Computer$_"
            Drives   = @()
        }

        1..10 | % { 
            $Computer.Drives += [PSCustomObject]@{ Letter = (65..90 | % { [char]$_ } | Get-Random); FreeSpace = (Get-Random -Minimum 1 -Max 500) }
        }

        $Computer
    }

    New-UDTable -Data $Computers -Columns @(
        New-UDTableColumn -Property Computer -Title "Computer" -Width 500
        New-UDTableColumn -Property Drives -Title "Drives" -Render {
            New-UDLayout -Columns 5 -Content {
                $EventData.Drives | Sort-Object -Property Letter | ForEach-Object {
                    New-UDElement -Tag Div -Content {
                        if ($_.FreeSpace -lt 100) {
                            New-UDAlert -Text "$($_.Letter): | $($_.FreeSpace) GB"  -Severity error
                        }
                        else {
                            New-UDAlert -Text "$($_.Letter): | $($_.FreeSpace) GB"
                        }
                    } -Attributes @{
                        style = @{ width = "200px"; margin = "5px" }
                    }
  
                }
            }
        } 
    )
}

1 Like

@adam - perfect thank you

1 Like

-OnRowExpand I am attempting to also show an additional column which shows percentage used which is calculated based on two values I already have e.g. $EventData.Size and $EventData.FreeSpace

For some reason though the calculation keeps giving me “Attempted to divide by zero” errors.

This doesn’t make sense as values definitely are NOT zero as confirmed by value already shown on screen and when using wait-debugger to check values before the error occurs.

For example

Values showing before error are

FreeSpace=418431135744
Size=500093153280

And command giving error is

$PercentFree = [Math]::Round(($EventData.FreeSpace / $EventData.Size) * 100, 2)

But running

$PercentFree = [Math]::Round((418431135744 / 500093153280) * 100, 2)

doesn’t give an error?

Also how do I get a value which is not part of my “$EventData” to appear in a column as even using -Render and trying to show something from a variable which isn’t part of $EventData the column just appears empty.

Thanks

 ) -OnRowExpand {
       

$PercentFree = [Math]::Round(($EventData.FreeSpace / $EventData.Size) * 100, 2)




        $Columns = @(
            New-UDTableColumn -Property DeviceID -Title "Drive Letter"
            New-UDTableColumn -Property VolumeName -Title "Volume Name" 
            New-UDTableColumn -Property Size -Title "Size" -Render {ConvertTo-ByteString ($EventData.Size)}
            New-UDTableColumn -Property FreeSpace -Title "Free Space" -Render {ConvertTo-ByteString ($EventData.FreeSpace)}
            New-UDTableColumn -Property PercentFree -Title "Percent Free" -Render {$PercentFree}
            )

            New-UDTable -Data $EventData.Group -Columns $Columns 
    }

}

What happens if you just calculate percent free in the Select-Object and then just display that?

$Cache:DiskReport = Get-CimInstance -Class Win32_LogicalDisk -Filter "Drivetype=3" -ComputerName $Servers -Property DeviceID, VolumeName, FreeSpace | Select-Object -Property PSComputerName, DeviceID, VolumeName, FreeSpace, @{l='PercentFree'; e={[Math]::Round(($_.FreeSpace / $_.Size) * 100, 2)}}

1 Like

@adam - thank you, perfect solution, works perfectly - I should have thought of that, think I was overthinking it. Wish I was as good at PowerShell as you! Thanks again

haha glad I could help! :blush: