Dashboard with UdCard and dynamically reload

Product: PowerShell Universal
Version: 1.4.6

    ###################################################################3
    New-UDCard -Title "Informacja" -Content {
        "asdasd"
    }

    New-UDGrid -Container -Children {
        $computerList = Get-Content -Path "D:\list.txt"
        foreach ($computerName in $computerList) {
            New-UDGrid -Item -Children {
            
                New-UDDynamic -Content { 
                    function Test-Computer {
                        [CmdletBinding()]
                        param (
                            [Parameter(HelpMessage = "ComputerName to test availabilitiy", ValueFromPipeline)]
                            [Alias("Computer")]
                            [string]$computerName
                        )
                        begin {
                            function Get-LocalGroupMembership {
                                [CmdletBinding()]
                                param (
                                    [Parameter(HelpMessage = "ComputerName do check group membership", ValueFromPipeline)]
                                    [Alias("Computer")]
                                    [string]$computerName,
                                    [Parameter(HelpMessage = "GroupName to check members")]
                                    [Alias("Group")]
                                    [string]$groupName
                                )
                                $members = @($([ADSI]"WinNT://$computerName/$groupName,group").psbase.Invoke("Members"))   
                                foreach ($member in $members) {
                                    $name = $member.GetType().InvokeMember("Name", 'GetProperty', $null, $member, $null)
                                    $name
                                }
                            }
                        }
                        process {

                            #Sprawdzenie, czy jest możliwe podłączenie się do stacji.
                            $isComputerReadyForTest = try {
                                $isConnected = Test-Path -Path "\\$computerName\C$" -ErrorAction SilentlyContinue
                                $adminMembers = (Get-LocalGroupMembership -Computer $computerName -Group "Administrators")
                                $rdpMembers = (Get-LocalGroupMembership -Computer $computerName -Group "Remote Desktop Users")
                                
                                ($isConnected -and ($adminMembers -contains "user.2") -or ($rdpMembers -contains "user.2"))
                            }
                            catch [System.Management.Automation.MethodInvocationException] {
                                Write-Warning "cannot test $computerName"
                                $false
                            }
                            $isComputerReadyForTest
                        }
                        
                    }

                    $udCardElements = if (Test-Computer -Computer $computerName) {
                        $header = New-UDCardHeader -Title $computerName -Avatar $(New-UDAvatar -Content { "T" } -Sx @{ backgroundColor = "#00FF00" }) 

                        function Test-LogonSessions {
                            param (
                                [Parameter(HelpMessage = "ComputerName to test logonsession", ValueFromPipeline)]
                                [Alias("Computer")]
                                [string]$computerName
                            )
                        
                            process {
                                $userFromStationCSV = (quser /server:$computerName 2>&1) -split "\n" -replace '\s{2,}', ','
                                if ($userFromStationCSV -match "ID") {
                                    $userObjects = $userFromStationCSV | ConvertFrom-Csv -Delimiter ','
                                    foreach ($userObject in $userObjects) {
                                        [PScustomObject]@{
                                            COMPUTERNAME = $computerName
                                            USERNAME     = $userObject.USERNAME
                                            TIME         = (Get-Date).ToString("dd.MM.yyyy_HH.mm")
                                            ID           = $userObject.ID
                                            STATE        = $userObject.STATE
                                        }
                                    }
                                }
                                else {
                                    [PScustomObject]@{
                                        COMPUTERNAME = $computerName
                                        USERNAME     = "BRAK"
                                        TIME         = (Get-Date).ToString("dd.MM.yyyy_HH.mm")
                                    }
                                }
                            }
                        }
                        function Get-IpAddress {
                            [CmdletBinding()]
                            param(
                                [Parameter(HelpMessage = "ComputerName to test IPAddress", ValueFromPipeline)]
                                [Alias("Computer")]
                                [string]$computerName
                            )
                            process {
                                $ipAddress = try {
                                (Get-CimInstance -Query "SELECT IpAddress FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = 'True'" -ComputerName $computerName).IpAddress[0];
                                }
                                catch {
                                (Get-WmiObject -Query "SELECT IpAddress FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = 'True'" -ComputerName $computerName ).IpAddress[0]
                                }
                                $ipAddress
                            }
                        }
                        $udListItemObject = [PSCustomObject]@{
                            LOGGED = $(Test-LogonSessions -Computer $computerName).USERNAME
                            IP     = $(Get-IpAddress -Computer $computerName).ToString()
                            TUXEDO = $(((Get-Content -Path "\\$computerName\C$\environment\file.environ" -ErrorAction SilentlyContinue)[4]).Replace(";*", " ").Trim(" ").ToString())
                        }

                        $body = New-UDCardBody -Content {
                            New-UDList -Children {
                                New-UDListItem -Label 'Logged' -Icon (New-UDIcon -Icon user -Size 1x) -SubTitle $($udListItemObject.LOGGED)
                                New-UDListItem -Label 'IP' -Icon (New-UDIcon -Icon laptop -Size 1x) -SubTitle $($udListItemObject.IP)
                                New-UDListItem -Label 'Environment' -Icon (New-UDIcon -Icon leaf -Size 1x) -SubTitle $($udListItemObject.TUXEDO)
                            }
                        }
                        [PSCustomObject]@{
                            HEADER = $header
                            BODY   = $body
                        }
                    }
                    else {
                        $header = New-UDCardHeader -Title $computerName -Avatar $(New-UDAvatar -Content { "F" } -Sx @{ backgroundColor = "#FF0000" }) #IDEA: -Subheader można coś tutaj dodać
                        $body = New-UDCardBody -Content {
                            New-UDList -Children {
                                New-UDListItem -Label 'Logged' -Icon (New-UDIcon -Icon user -Size 1x) -SubTitle $($udListItemObject.LOGGED)
                                New-UDListItem -Label 'IP' -Icon (New-UDIcon -Icon laptop -Size 1x) -SubTitle $($udListItemObject.IP)
                                New-UDListItem -Label 'Environment' -Icon (New-UDIcon -Icon leaf -Size 1x) -SubTitle $($udListItemObject.TUXEDO)
                            }
                        }
                        [PSCustomObject]@{
                            HEADER = $header
                            BODY   = $body
                        }
                    }

                    New-UDCard -Header $($udCardElements.HEADER) -Body $($udCardElements.BODY) -Sx @{width = 300; maxWidth = 300; border = '2px solid #f0f2f5' } -Id 'card'
                } -AutoRefresh -AutoRefreshInterval $(60 * 5)
                    
            }
        }
    }

Hi I am obviously as green as frog in PowerShell Universal\PowerShell DAshboard\something with dashboard but i need help.

WHAT I WANT TO DO:
I want to do dashboard with all computers in my test env (400) which is readed from txt file.
In that UD Card I want to have:
-Header - computerName
-Avatar - changed dynamicaly every few seconds/few minutes letter and color T(green) or F(red) depends if computer is Test-Connect
-Body- In body i want to have UdList where i want to have:
–UDLabel name Logged changed dynamically depends which user is logged on station with specific icon
–UDLabel name Ip with ip
–UDLabel name Environment changed dynamically which information which environment is on station. The environment check is made every few seconds/minutes.

CONCLUSION
As you see i create that code, but the problem is i do not know how to edit it. Now it works but it is slow, and i want to make it faster. Can someone edit it and explain how and why is edited every piece of code?

I would suggest taking a look at New-UDDynamic [1][2] for some help defining dynamic regions. It may also help to take a look at the React.js component structure to understand how dynamic content will be handled under the hood. It may not be helpful to you, but as a React developer it helps me to understand how the definitions I’m making in PowerShell influence the app that is displayed, and helps me structure my apps in a way that benefits from the React model.

To be honest i know the theory, but rather now i am looking for someone to help me with that code and correct it and explain why it works like that.

This is slow due to all the commands you are running against all the computers. It needs to do it for each computer before any UI is shown.

One thing I would recommend would be running this script in the background on a schedule and caching the data to prevent delay in the UI. Since it doesn’t seem to take any user input, the user could just bring up the page pretty much instantly.

If there is some reason not to do it that way, let me know, otherwise I’m happy to put together an example.

I thought about do it like this:
1.generate all static data(data that are not reload)
2.create scopes for that that must reload.

But to be honest i am new for this. I created the code above only by using examples in docs. And now i dont know how to make it work properly.

It will be helpfull if you redesign my code with explanation :slight_smile:

I’m writing this after having written everything else - my tone seems a little absolute and/or confrontational and I do not mean it to be! There are many points I touch on here and I am far from an expert, so I may be inaccurate in places just due to my own lack of knowledge. So, grain of salt. Also, I’m not meaning to be mean or aggressive either - I just wrote it a bit matter-of-factly. This is an interesting problem! So —

Typically forums such as these are meant more for abstract problems, or rather ways to solve those problems. I’ll do my best to abstract this out into a set of steps but redesigning your app would require anyone who attempts to take on the task to set up a test environment to mimic yours with all of the devices and their dynamic properties… it is unfeasible.

So, you are loading a dynamic card for a computer, which has a number of both static and dynamic properties associated. This you have done well!

Then, we want to scale it up - however, you are running the calculations for these properties on every device, of a set of ~400 - so multiply the computation required by 400, and then rerunning the calculations for your properties every 5 minutes. You’re already seeing the problem with this in that it takes a long time to get these properties.

You can optimize your code - for example, you use judicious use of the pipeline, which has some performance implications for larger data processing. I would recommend changing lines like this:
$userObjects = $userFromStationCSV | ConvertFrom-Csv -Delimiter ','
to something akin to:
$userObjects = ConvertFrom-Csv -InputObject $userFromStationCSV
Notice I’ve omitted the delimiter parameter as the default delimiter for a comma-separated file is, well, a comma. However, small performance optimizations like this will likely not be enough.

You’re now left with a pretty big decision of priorities - you can’t see all of the properties for all of these devices with a recency margin of only 5 minutes. One of those parameters has to give.

Like you said, it would be a good idea to generate static data and hold it in some place (caching), but I’m not sure how much of that you will have given the amount of dynamic data - basically everything in the card body has to be recalculated every time you want to refresh.

I would recommend changing your approach to a simple grid output (look at New-UDDataGrid [1] for example) pulling all of the static information, with the card generation taking place within the -LoadDetailedContent event handler.

You’d need to translate your txt file into something more table-friendly: I’d recommend creating a script run on a schedule (a job) to perform the initial data gathering and format it out into a CSV or JSON, or even CLIXML if you’re feeling up to it. Any data format that can easily hold 2(+) dimensions of data. For this case, I’d recommend a CSV for simplicity, or CLIXML if you prefer better serialization support. This job will perform your calculations and save them to a file locally on the machine. Then, your app will reference that file to load data into the DataGrid, with the card being created when the row is expanded that way the more complicated and dynamic time-based calculations can be run as soon as the device is selected, running the calculations for that device alone instead of all 400 at once. So, with this proposal, your app structure would look like:

  • A script running once per hour/day, reading D:\list.txt and translating it to a CSV with some more helpful data for each device. You could include the IP here, or semi-recent user data, or anything else.
  • An app that reads the CSV and transforms it into a datagrid
  • A datagrid component with a -LoadDetailedContent event handler
  • on expand → your code to generate the card

This is a lot of work for sure. Please let me know if you need help on any specifics, but this is already getting long-winded and circular. I can also hopefully provide some more examples if you have questions on the specifics but I’d recommend taking a stab at it and seeing what improvements you can make!
:wavy_dash:ZG