Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New-UDTable cannot use properties of classes as content #209

Closed
bateskevin opened this issue Jun 5, 2018 · 9 comments

Comments

Projects
None yet
3 participants
@bateskevin
Copy link

commented Jun 5, 2018

Hi!

I tried to create a table that would display me the properties of the disks of my computer. I wrote a class in Powershell 5.1 that contains the properties of the disks and tried to dynamically create the tables. The Dashboard won't load the values correctly tho. When clear my object each time I get only empty fields in my table at the end, but when I don't clear the object it will show me the same values in every table. Hardcoded values work no problem.

Could it be, that UD just keeps the data of the latest object and then reffers to that one?

The Code is at the end of the message.

Kind Regards,
Kevin

Import-Module "C:\Admin\Scripts\UniversalDashboard\1.6.1\UniversalDashboard.psd1" -Force
Class Disk {
    [String]$DriveLetter
    [int]$DiskNumber
    [String]$PartitionNumber
    [String]$Size
    [String]$SANID
    [String]$SCSIBus
    [bool]$MountPoint = $false
    Disk ([String]$DriveLetter){
        $Drive = Get-Disk | Get-Partition | where DriveLetter -eq $DriveLetter
        if($Drive -ne $null){
            $DiskInformation = Get-Disk | Get-Partition | where DriveLetter -eq $DriveLetter | Select DriveLetter,DiskNumber,PartitionNumber,Size
            $this.DriveLetter = $DiskInformation.DriveLetter
            $this.DiskNumber = $DiskInformation.DiskNumber
            $this.PartitionNumber = $DiskInformation.PartitionNumber
            $this.Size = $DiskInformation.Size
            $this.MountPoint = $false
        }else{
            Write-Verbose "The Disk $DriveLetter is not present."
        }
    }
    [void] Update() {
        try{
            Update-Disk -Number $this.DiskNumber -ErrorAction Stop
        }catch{
            throw $_
        }
    }
    [void] ClearInstance() {
        $this.DriveLetter = $null
        $this.DiskNumber = $null
        $this.PartitionNumber = $null
        $this.Size = $null
        $this.MountPoint = $null
    }
}
$DiskManagement = New-UDDashboard -Title "PS Disk Management Test" -Content {
    New-UDCard -Title "PS Disk Management" 
    $Disks = (Get-Disk | Get-Partition).DriveLetter
    $DiskArray = @()
    foreach($Disk in $Disks){        
        $CDisk = [Disk]::new("$Disk")
        $DiskArray += $CDisk
    }
    foreach($Disk in $DiskArray){ 
       New-UDTable -Title "Information about Disk $($Disk.DriveLetter)" -Headers @("ComputerName", "OperatingSystem","Size",'FreeDiskSpace','Disknumber','PartitionNumber') -AutoRefresh -RefreshInterval 30 -Endpoint {
        $props = @{
           'ComputerName' = $env:COMPUTERNAME
           'OperatingSystem' = (Get-CimInstance -ClassName Win32_OperatingSystem).Caption
           'Size' = (Get-Disk | Get-Partition | where DriveLetter -eq $Disk.DriveLetter).Size
           'FreeDiskSpace' = (Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DeviceID='$($Disk):'").FreeSpace / 1GB | ForEach-Object { "$([Math]::Round($_, 2)) GBs " }
           'Disknumber' = $Disk.DiskNumber
           'PartitionNumber' = $Disk.PartitionNumber
       }
         $obj = New-Object psobject -Property $props
         $obj | Out-UDTableData -Property @("ComputerName", "OperatingSystem","Size",'FreeDiskSpace','Disknumber','PartitionNumber')
        }
    #$CDisk.ClearInstance()
    }
}
Start-UDDashboard -Port 1000 -Dashboard $DiskManagement -Name "PS Disk Management" -AutoReload

@adamdriscoll adamdriscoll added the bug label Jun 5, 2018

@adamdriscoll

This comment has been minimized.

Copy link
Member

commented Jun 5, 2018

There seems to be some sort of variable scoping issue going on. I've seen this in similar loops. I'll have to look into it.

@rdbartram

This comment has been minimized.

Copy link

commented Aug 16, 2018

You need to get a little creative because right now there is no way to pass params to an endpoint...I think that might be a cool idea, but that's another discussion.

Here is a working copy of what you want. I tried to change as little as possible, but it could be argued that the idea with a table per disk is wrong...anyway....working code sample.

$DiskManagement = New-UDDashboard -Title "PS Disk Management Test" -Content {
    New-UDCard -Title "PS Disk Management"

    New-UDElement -Tag 'div' -Endpoint {
        $Disks = (Get-Disk | Get-Partition).DriveLetter
        $DiskArray = @()
        foreach ($Disk in ($Disks| ? { [char]::IsLetter($_)})) {
            $CDisk = [Disk]::new("$Disk")
            $DiskArray += $CDisk
        }

        foreach ($Disk in $DiskArray) {
            New-UDElement -Tag "div" -Id $Id -Attributes @{
                className = 'card ud-table'
            } -Content {
                New-UDElement -Tag "div" -Attributes @{
                    className = 'card-content'
                } -Content {
                    New-UDElement -Tag 'span' -Content { "Information about Disk $($Disk.DriveLetter)" }
                    New-UDElement -Tag 'table' -Content {
                        New-UDElement -Tag 'thead' -Content {
                            New-UDElement -Tag 'tr' -Content {
                                foreach ($header in @("ComputerName", "OperatingSystem", "Size", 'FreeDiskSpace', 'Disknumber', 'PartitionNumber')) {
                                    New-UDElement -Tag 'th' -Content { $header }
                                }
                            }
                        }
                        New-UDElement -Tag 'tbody' -Content {
                            $props = @{
                                'ComputerName'    = $env:COMPUTERNAME
                                'OperatingSystem' = (Get-CimInstance -ClassName Win32_OperatingSystem).Caption
                                'Size'            = (Get-Disk | Get-Partition | where DriveLetter -eq $Disk.DriveLetter).Size
                                'FreeDiskSpace'   = (Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DeviceID='$($Disk):'").FreeSpace / 1GB | ForEach-Object { "$([Math]::Round($_, 2)) GBs " }
                                'Disknumber'      = $Disk.DiskNumber
                                'PartitionNumber' = $Disk.PartitionNumber
                            }
                            $obj = New-Object psobject -Property $props
                            $obj | Out-UDTableData -Property @("ComputerName", "OperatingSystem", "Size", 'FreeDiskSpace', 'Disknumber', 'PartitionNumber')
                        }
                    }
                }
            }
        }
    } -AutoRefresh
} -EndpointInitializationScript {
    Class Disk {
        [String]$DriveLetter
        [int]$DiskNumber
        [String]$PartitionNumber
        [String]$Size
        [String]$SANID
        [String]$SCSIBus
        [bool]$MountPoint = $false
        Disk ([String]$DriveLetter) {
            $Drive = Get-Disk | Get-Partition | where DriveLetter -eq $DriveLetter
            if ($Drive -ne $null) {
                $DiskInformation = Get-Disk | Get-Partition | where DriveLetter -eq $DriveLetter | Select DriveLetter, DiskNumber, PartitionNumber, Size
                $this.DriveLetter = $DiskInformation.DriveLetter
                $this.DiskNumber = $DiskInformation.DiskNumber
                $this.PartitionNumber = $DiskInformation.PartitionNumber
                $this.Size = $DiskInformation.Size
                $this.MountPoint = $false
            } else {
                Write-Verbose "The Disk $DriveLetter is not present."
            }
        }
        [void] Update() {
            try {
                Update-Disk -Number $this.DiskNumber -ErrorAction Stop
            } catch {
                throw $_
            }
        }
        [void] ClearInstance() {
            $this.DriveLetter = $null
            $this.DiskNumber = $null
            $this.PartitionNumber = $null
            $this.Size = $null
            $this.MountPoint = $null
        }
    }
}

Start-UDDashboard -Port 1000 -Dashboard $DiskManagement -Name "PS Disk Management"

@adamdriscoll adamdriscoll added this to the 2.0.0 milestone Aug 16, 2018

@adamdriscoll

This comment has been minimized.

Copy link
Member

commented Aug 16, 2018

You should be able to pass variables to endpoints but like I mentioned above, there is a scoping issue preventing this from working successfully in a lot of circumstances. The circumstance I see the most frequently , loops, is also the one that doesn't work well.

So you're right, I think that there needs to be a different way to do this. The current implementation really fights with the PowerShell engine to get variables passed into the endpoints correctly.

Not only do variables end up not being set, global variables like the ones used in Azure and VMware, cause all kinds of problems due to their global scope or readonly nature.

@rdbartram

This comment has been minimized.

Copy link

commented Aug 16, 2018

@adamdriscoll variables isn't my problem, I meant somehow being about to pass parameters with the scriptblock during execution. I often use variables across the endpoint boundary but like you said with loops it breaks.

@adamdriscoll

This comment has been minimized.

Copy link
Member

commented Aug 16, 2018

@rdbartram I guess I'm not understanding the difference. If you had a parameter on the endpoint or a variable "magically" added to the endpoint, you'd still have a variable that you could use in the endpoint.

Can you give me a simple example of what you mean? Sometimes it's hard to see the forest from the trees...

@rdbartram

This comment has been minimized.

Copy link

commented Aug 16, 2018

I wish I could corner you for a while to chat through a lot of these things. The example I had was as follows

foreach($item in $items) {
    New-UDTable -Title "Item $($item.id)" -Headers @(" ", " ") -AutoRefresh -Endpoint {
        $Item.GetEnumerator() | Out-UDTableData -Property @("Name", "Value")
    }
}

the problem here is that $item in the endpoint is executed at runtime and will only ever return the last element in the $items array (as kevin mentioned).

However, if we could pass a parameter the syntax would look as follows and the state of the parameter is saved in the callback to be passed at runtime.

foreach($item in $items) {
    New-UDTable -Title "Item $($item.id)" -Headers @(" ", " ") -AutoRefresh -Endpoint {
        param ([hashtable]$Item)
        $Item.GetEnumerator() | Out-UDTableData -Property @("Name", "Value")
    } -EndpointParams @($Item)
}

Maybe I'm being super blonde but I think there is a difference between the two.

@adamdriscoll

This comment has been minimized.

Copy link
Member

commented Aug 16, 2018

Variables that are within the scope of an endpoint as supposed to be saved and passed at runtime. The loop example is an example of setting a variable into the execution context of endpoint and the PowerShell engine not honoring that Set-Variable call for whatever reason. I never really got to the bottom of it.

The original idea was to make it all seamless but I think an explicit parameter for variables is a better idea.

What if we called it -ArgumentList rather than -EndpointParams to be consistent with other PowerShelly things?

@rdbartram

This comment has been minimized.

Copy link

commented Aug 16, 2018

I 100% knew you was going to say argumentlist after I clicked send. I agree it is also the better option... Was just a poc ;). I'm still getting the grips with the back end code. Haven't quite got the end routing system working. I hope in time to be able to help out more.

@adamdriscoll

This comment has been minimized.

Copy link
Member

commented Aug 18, 2018

I've added -ArgumentList to all cmdlets with an Endpoint. You can also use New-UDEndpoint to pass arguments to an event handler.

For example:

 New-UDButton -Text 'Patch' -Id 'button' -OnClick (
                New-UDEndpoint -Endpoint { Start-Process $ArgumentList[0] } -ArgumentList $Name
            )

I struggled for quite some time to get the $args variable to be set correctly within the endpoint. I settled on just setting a variable $ArgumentList. I'll revisit in the future if this causes problems.

This will work now:

foreach($item in $items) {
    New-UDTable -Title "Item $($item.id)" -Headers @(" ", " ") -AutoRefresh -Endpoint {
        $ArgumentList[0].GetEnumerator() | Out-UDTableData -Property @("Name", "Value")
    } -ArgumentList @($Item)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.