Posted on Leave a comment

Serving Files with Universal Dashboard

In order to serve files with Universal Dashboard, you need to take advantage of the Publish-UDFolder feature. In this blog post we will look at how to provide download links for users so that they can download static files as well as files you generate based on user input.

Sharing a Folder

Sharing a folder with Universal Dashboard is easy. Publish-UDFolder allows you to specify a local file path as well as a URL that the user will access to download files.
In this example, I am publishing a folder called Share in my $PSScriptRoot path. The user can request files from the share using standard HTTP clients like web browsers or invoke web request.

$Folder = Publish-UDFolder -Path "$PSScriptRoot\share" -RequestPath "/share"
$Dashboard = New-UDDashboard -Title 'Downloads' -Content {

} 
Start-UDDashboard -Dashboard $Dashboard -PublishedFolder $Folder -Port 10000

Once the dashboard is started, you could request files from the server by accessing it via URLs such as http://localhost:10000/share/users.txt.

Invoke-WebRequest http://localhost:10000/share/users.txt

Providing a Download Link on the Dashboard

To provide a link to files in the dashboard, you can use standard links with URLs that point to the files you would like to share. You’ll notice that when you click this it will open the file in the browser.

$Dashboard = New-UDDashboard -Title 'Downloads' -Content {
    New-UDLink -Text "Download" -Url http://localhost:10000/share/users.txt
} 

To force a download of the file rather than opening in the browser, you can use New-UDElement to create an anchor tag with the download attribute. This will instruct the browser to download the file rather than opening it.

New-UDElement -Tag 'a' -Attributes @{
    'href' = 'http://localhost:10000/share/users.txt'
    'download' = 'myFileName.txt'
} -Content {
    "Download"
}

Dynamic File Downloads

Sometimes you will want to generate a file from input from the users and then allow them to download it. You can do this by providing a variable to the shared folder path and write files to that path. After writing the files to the path, use the -Content parameter of New-UDInputAction to return a download button to the user.

$SharePath = "$PSScriptRoot\share"
$EndpointInit = New-UDEndpointInitialization -Variable "SharePath" 
$Dashboard = New-UDDashboard -Title 'Downloads' -Content {

    New-UDInput -Title "Create File" -Endpoint {
        param($FileContents)

        $FileName = (New-Guid).ToString() + ".txt"
        $FullFileName = Join-Path $SharePath $FileName
        $FileContents | Out-File -FilePath $FullFileName

        New-UDInputAction -Content (
            New-UDElement -Tag 'a' -Attributes @{
                'href' = "http://localhost:10000/share/$FileName"
                'download' = "myFileName.txt"
                className = "btn"
            } -Content {
                "Download"
            }
        )
    }
} -EndpointInitialization $EndpointInit

You can then use a scheduled endpoint to clean up files that have been generated when users create them.

$Schedule = New-UDEndpointSchedule -Every -Minute 10
$Endpoint = New-UDEndpoint -Schedule $Schedule -Endpoint {
    Get-ChildItem -Path $SharePath | Remove-Item
}

Start-UDDashboard -Dashboard $Dashboard -PublishedFolder $Folder -Endpoint $Endpoint -Port 10000

Conclusion

In this blog post we looked at how to create published folders and share files with users. We also went over how to create download links and to provide dynamic files to your end users.

Posted on Leave a comment

Understanding Authorization Policies in Universal Dashboard

This post discusses features available in Universal Dashboard

Authorization policies in Universal Dashboard allow you to control the content that particular users have access to. You can limit access to the entire dashboard, to pages themselves or even to individual controls. In this post we will go over how to use authorization policies to create secure dashboards.

The Basics

Authorization policies are tightly linked with authentication. After authentication takes place, the user has a series of claims associated with their session. These claims can be evaluated to determine whether a user has access to a particular resource.

Depending on how you authenticate to your dashboard, the claims the user has will be different. This means that it’s necessary to understand the claim system in order to effectively take advantage of authorization policies. In this post, we will use Azure Active Directory authentication as our example.

Configuring Azure Active Directory

To configure Universal Dashboard for Azure Active Directory authentication, we need to use the New-UDAuthenticationMethod cmdlet alongside the New-UDLoginPage cmdlet. You’ll need to get your ClientId, Instance URI, Domain and TenantId from the Azure portal. You’ll also need to configure an application registration within your directory. The process for that is covered in this document.

Once you’ve registered your application, you can now write the script for your dashboard. You’ll need to use the following script to do so.

$AuthenticationMethod = New-UDAuthenticationMethod -ClientId '1111e4b-45aa-43bb-beae-304028707777' -Instance https://login.microsoftonline.com -Domain ironmansoftware.onmicrosoft.com -TenantId '11111c97-4b76-4470-a736-8481d71111111'
$LoginPage = New-UDLoginPage -AuthenticationMethod $AuthenticationMethod 

You can now pass the $LoginPage variable to your dashboard and start it. Make sure to listen on the same port as the one you configured in AzureAD.

$Dashboard = New-UDDashboard -Title "Authorization" -Content {

} -LoginPage $LoginPage 
Start-UDDashboard -Port 10000 -Dashboard $Dashboard -AllowHttpForLogin

When visiting your dashboard, you should now see a login page with a “Sign in with Azure Active Directory” button.

Azure AD Login

Once you click the button, you will be forwarded to the Microsoft login page for your directory. Once you login, you will be forwarded back to Universal Dashboard. You should now see the home page.

Authorized Home Page

Configuring an Authorization Policy

To configure an authorization policy, you need to use the New-UDAuthorizationPolicy cmdlet. This cmdlet accepts a script block that will run to evaluate whether the current user has access to the resource they are trying to retrieve.

An authorization policy simply needs to return $true or $false. If the policy throws an error, it is considered $true so ensure that you wrap your code in a try\catch if you do not wish to see this behavior.

The most basic authorization policy is as follows.

$AuthorizationPolicy = New-UDAuthorizationPolicy -Name "Login" -Endpoint {
    $true
}

This authorization policy always returns true and and is named “Login”. You will use this name whenever you want to enforce the authorization policy.

You can now ensure your dashboard uses this authorization policy by passing it to New-UDLoginPage.

$LoginPage = New-UDLoginPage -AuthenticationMethod $AuthenticationMethod -AuthorizationPolicy $AuthorizationPolicy

Using the Authorization Policy

To use the authorization policy, you need to assign it to either a page or to take advantage of the Get-UDAuthorizationPolicy cmdlet. When you pass the name of the authorization policy to New-UDPage, it will ensure that only users that pass the authorization policy will have access to that page. When you invoke Get-UDAuthorizationPolicy from within your dashboard, you will get all the authorization policies that the user has passed.

To use it on a page, simply pass the name of the policy to the -AuthorizationPolicy parameter of New-UDPage. This parameter accepts an array of policies if you desire.

As you can see below, we have a page that has an authorization policy assigned and one that does not.

$Settings = New-UDPage -Name Settings -AuthorizationPolicy "Login" -Content {

}
$HomePage = New-UDPage -Name "Home" -Content {

}

$Dashboard = New-UDDashboard -Title "Authorization" -Pages @($HomePage, $Settings) -LoginPage $LoginPage 

When you login, you’ll notice that you have access to both pages. If you go back and change your authentication policy to return $false, you will no longer have access to the settings page. If you try to access the page directly by URL, it will return Page Not Found.

You can also use authorization policies to hide controls on your dashboard. To do this, you can use Get-UDAuthorizationPolicy and then check to see whether a particular policy is returned before returning a control. This needs to be done dynamically so it needs to be in an endpoint script block rather than content.

If I modify my home page to show two columns of controls, I can use Get-UDAuthorizationPolicy to check to see if I should return a control before showing it to the user.

$HomePage = New-UDPage -Name "Home" -Content {
    New-UDRow -Endpoint {
        New-UDColumn -Size 6 -Content {
            New-UDHeading -Text 'Super Public Info' -Size 1
        }
        New-UDColumn -Size 6 -Content {
            $Policy = Get-UDAuthorizationPolicy
             if ($AuthPolicy -contains 'Login') {
                New-UDHeading -Text 'Super Secret Info' -Size 1
            }  
        }
    }
}

If you run the dashboard now, you’ll see that the super secret info is not shown. If you again change your authorization policy back to return $true, you will see the super secret info. You can use this technique anywhere an Endpoint parameter is present.

Checking Claims

Authorization policies would be pretty useless if you didn’t actually check some information about the user. To do this, we take advantage of the claims provided by the authentication mechanism we are using. In this case, Azure Active Directory is returning a bunch of claim information to us. To access this information, we can adjust our authorization policy endpoint to accept a $User parameter. This variable is an instance of an IClaimsPrincipal class. You can use this variable to check whether a user has particular rights.

In our authorization policy, we can now use that variable and call methods such as HasClaim to validate whether a user has a particular claim.

$AuthorizationPolicy = New-UDAuthorizationPolicy -Name "Login" -Endpoint {
    param($User)
    $User.HasClaim("groups", "b69421c1-381f-41e9-9105-1ed85768cde1")
}

In my environment, the GUID listed is the Object ID of a group that the user is a part of.

Azure AD Group

HasClaim will return true if the user is part of that group. Now, when a user that logs into the dashboard is part of the Dashboard Users group, they will have access to the Settings page and the Super Secret Info control.

Debugging Claim Checks

One of the problems with claims is that they are cryptic and change per authentication mechanism. In order to accurately check claims you will likely want to evaluate the $User variable in the debugger to see what claims are present when a user logins in. To do this, we need to call Wait-Debugger in the New-UDAuthorizationPolicy endpoint.

 $AuthorizationPolicy = New-UDAuthorizationPolicy -Name "Login" -Endpoint {
    param($User)

    Wait-Debugger
    $User.HasClaim("groups", "e1b6e95e-6241-4a1a-886d-d5fc0f606f99")
}

When you go to login to your dashboard, you will now see only some of it load. Additionally, the console output will indicate that it has entered debug mode.

Debugging Authorization Policies

If you now type the $User variable and press enter, you will see the object is present in the runspace. You’ll be able to dump the claims that are currently set on the object. This will provide you insight into how to correctly check for the claims of the user.

[DBG]: PS C:\Users\adamr>> $User.Claims

Issuer         : https://sts.windows.net/3ee40c97-4b76-4470-a736-8481d7a2ed87/
OriginalIssuer : https://sts.windows.net/3ee40c97-4b76-4470-a736-8481d7a2ed87/
Properties     : {[http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/ShortTypeName, amr]}
Subject        : System.Security.Claims.ClaimsIdentity
Type           : http://schemas.microsoft.com/claims/authnmethodsreferences
Value          : pwd
ValueType      : http://www.w3.org/2001/XMLSchema#string

Issuer         : https://sts.windows.net/3ee40c97-4b76-4470-a736-8481d7a2ed87/
OriginalIssuer : https://sts.windows.net/3ee40c97-4b76-4470-a736-8481d7a2ed87/
Properties     : {}
Subject        : System.Security.Claims.ClaimsIdentity
Type           : groups
Value          : b69421c1-381f-41e9-9105-1ed85768cde1
ValueType      : http://www.w3.org/2001/XMLSchema#string

Issuer         : https://sts.windows.net/3ee40c97-4b76-4470-a736-8481d7a2ed87/
OriginalIssuer : https://sts.windows.net/3ee40c97-4b76-4470-a736-8481d7a2ed87/
Properties     : {}
Subject        : System.Security.Claims.ClaimsIdentity
Type           : name
Value          : Adam Driscoll
ValueType      : http://www.w3.org/2001/XMLSchema#string

Conclusion

In this post we went through how to configure your dashboard to use authentication and authorization policies to validate user claims against Azure Active Directory. The same process holds true for configuration policies for other authentication methods. Check out the docs to learn more.

Posted on Leave a comment

Processing CSV Files with Universal Dashboard

Universal Dashboard is a web framework for PowerShell. You can download it from the PowerShell Gallery.

Universal Dashboard provides the ability to create user input forms using the New-UDInput cmdlet. In this post we will look at how to create a form that allows the user to upload a CSV file and process it within PowerShell.

UDInput Basics

New-UDInput has two different ways of functioning. The first is to use a param block to define an input form directly from the PowerShell script you specify.
In the below example, I can create a basic for that accepts a couple different fields.

New-UDInput -Title 'New User Form' -Endpoint {
    param(
        $FirstName,
        $LastName,
        $UserName
    )

    New-ADUser -GivenName $FirstName -Surname $LastName -Name $UserName
}

This script would produce the following form.

Form generated from param block

Although this type of form is easy to produce, it doesn’t provide the same level of customization as the -Content parameter of UDInput.

UDInput Content

The -Content parameter can be used in conjunction with the -Endpoint parameter to define the fields that show up in the form. You have more options when it comes to the type of form when using this method.

Instead of creating a form that accepts a single user, we may want a form that accepts a CSV and then creates many users. Just as with the basic form, you’ll still need to provide an -Endpoint script block for processing the input data. Unlike the basic form, the actual field definitions come from the -Content block rather than the param block in the Endpoint script block.

New-UDInput -Title 'Bulk Import User Form' -Content {
    New-UDInputField -Type file -Name users 
} -Endpoint {
    param($users)

    $UserObjects = $users | ConvertFrom-Csv

    $UserObjects | ForEach-Object {
        New-ADUser -GivenName $_.FirstName -Surname $_.LastName -Name $_.UserName
    }

    Show-UDToast -Message "Created $($UserObjects.Length) users"
}

This will produce a form like this.

Form created with Content and New-UDInputField

After uploading a file, the users will be created in Active Directory and a toast will be shown to the end user.

Uploading a CSV to New-UDInput

Conclusion

In this post we went through how to upload and process a CSV using New-UDInput. Using the -Content parameter you have more control over the types of input controls that you can use in your dashboards.

Posted on Leave a comment

Dude, where’s my var? – Understanding scoping in Universal Dashboard

Universal Dashboard is a web framework for PowerShell.

Universal Dashboard allows users to create websites and REST APIs using just PowerShell. Users of UD expect that it behaves much like any other PowerShell script as it looks just like a PowerShell script. To ensure that the web server provides as much performance as possible, Universal Dashboard uses background runspaces to allow for concurrent execution of script blocks that define the dynamic functionality of the dashboard. Due to this, variable, module and function scoping can be a little weird when dealing with UD.

In this blog post, we will look into the intricacies of scoping with Universal Dashboard endpoints.

What’s in a runspace?

To understand how Universal Dashboard functions, we need to understand a little bit about the runspace feature of PowerShell. Runspaces are somewhat isolated containers for PowerShell execution. When you start a PowerShell console, you will be invoking your commands in the default runspace. You can view runspaces by using the Get-Runspace command.

Windows PowerShell
 Copyright (C) Microsoft Corporation. All rights reserved.
 
 PS C:\> Get-Runspace
 
  Id Name            ComputerName    Type          State         Availability
  -- ----            ------------    ----          -----         ------------
   1 Runspace1       localhost       Local         Opened        Busy

Runspaces allow for a single pipeline to be executing at once. This means you can’t run two commands in the same runspace. Because of this, runspaces can be kind of synonymous with threads in other programming languages.

In addition to controlling the execution of a script, they also maintain the constructs that we are used to in any PowerShell environment. Variables, functions, modules, and providers are examples of artifacts that are tied to a runspace. Most of the time we are working with PowerShell, we are dealing with a single runspace so these types of artifacts seem to to be exist indefinitely, and globally, in the environment.

Note there are scopes within the runspace that control the lifetime and accessibility of these artifacts. Read about_Scopes for more information.

When we create a new runspace, that means we can now execute another PowerShell command in tandem and have a completely new set of variables, modules, and functions in that runspace’s scope.

A good way to demonstrate this is to use the ThreadJob module. This module creates new runspaces in the background so that multiple commands can be run at once. The difference between a standard PowerShell job and a runspace job is that a standard PowerShell job actually starts a new process rather than creating a new runspace.

If we start a thread job and then use the Get-Runspace command, you’ll see that we now have more than one runspace.

PS C:\> Start-ThreadJob -ScriptBlock { Start-Sleep 10 }
Id Name PSJobTypeName State HasMoreData Location Command
1 Job1 ThreadJob Running False PowerShell Start-Sleep 10
PS C:\> Get-Runspace
Id Name ComputerName Type State Availability
1 Runspace1 localhost Local Opened Busy
2 Runspace2 localhost Local Opened Busy

To demonstrate how each runspace has its own scope, let’s create a variable in the default runspace and then try to access it in the background runspace.

As you’ll see below, the Receive-Job call did not return a value. This is because $MyVar does not exist in the background runspace.

PS C:\> $MyVar = "Test"
PS C:\> Start-ThreadJob -ScriptBlock { $MyVar } | Wait-Job | Receive-Job
PS C:\>

One thing to note is that runspaces are different than .NET variable scoping. You can define static variables in .NET that are available across runspaces. Assemblies loaded into a PowerShell process are also not tied to a runspace but global throughout the process. Using this knowledge allows us to transfer a variable state across runspaces.

Runspace Initialization

It’s possible to create a runspace and initialize the runspace with lots of different PowerShell artifacts. We can pass in variables, functions, modules and even snap-ins (remember those!?).

This is accomplished with the InitialSessionState class. It provides the ability to define the state of the runspace when it’s created. The ThreadJob module takes care of this for us. UD also has a helper to set up the initial session state. The New-UDEndpointInitialization cmdlet actually creates an InitialSessionState object that is then used to initialize the runspaces it uses when executing endpoints.

PS C:\> New-UDEndpointInitialization -Variable MyVar | Get-Member
 
    TypeName: System.Management.Automation.Runspaces.InitialSessionState

If we look at the InitialSessionState returned by New-UDEndpointInitialization you’ll see that the variable is set.

PS C:\> $InitialSessionState = New-UDEndpointInitialization -Variable MyVar
 PS C:\> $InitialSessionState.Variables | Where-Object Name -eq 'MyVar'
 
 
 Value       :
 Description :
 Options     : None
 Attributes  : {}
 Visibility  : Public
 Name        : MyVar
 PSSnapIn    :
 Module      :

Whenever you execute an endpoint in UD it now has access to this variable because it’s part of the initial session state.

The InitialSessionState class has more options that are available with New-UDEndpointInitialization. You can always call the object directly to add more artifacts to the session state.

PS C:\> $InitialSessionState = New-UDEndpointInitialization -Variable MyVar
PS C:\> $InitialSessionState.Assemblies.Add("System.Windows.Forms.dll")

Runtime Variables

Although initial session state is valuable when passing in global variables you’d like to use throughout your dashboard, they aren’t good for variables that are set during runtime or change based on data brought into the script. UD has a couple of different ways of setting these variables during execution.

Auto-scoped variables

Auto-scoped variables are variables that are available in the child-endpoints. An example of this is where you have an endpoint create a control that also has an endpoint. This requires UD to pass in the variable during execution of the endpoint script block.

For example, you might have a dynamic UDColumn that creates a UDGrid. In this case, we have two different endpoints. If you read the last post on performance, you’ll know that this actually results in two HTTP requests and thus executes two script blocks independent of each other; one for each Endpoint.

New-UDColumn -Endpoint {
     $Data = Get-Data
     New-UDGrid -Title 'Data' -Endpoint {
         $Data | Out-UDGridData
     }
 }

When New-UDGrid is executed, the Endpoint is stored internally as a string.

Additionally, New-UDGrid (or any cmdlet with an Endpoint parameter) then look at the script block to see which variables are defined within it. In this case, it finds the $Data variable is used. It then calls Get-Variable to get the value of $Data and stores that along with the Endpoint script block string.

When the endpoint itself is actually executed, the endpoint script is then parsed back into a script block. Then the $Data variable is set as part of that execution so it is available when the endpoint is running.

This works the same for all the built-in dynamic variables such as $Response, $Request, and $User.

You can think of the resulting endpoint being executed as something like this.

Set-Variable -Name 'Data' -Value $DataFromAnotherRunspace
Set-Variable -Name 'Request' -Value $RequestFromAnotherRunspace
Set-Variable -Name 'Response' -Value $ResponseFromAnotherRunspace
$Data | Out-UDGridData

This effectively moves the variable from one runspace to another. After a runspace is executed, ResetRunspace is called to reset the runspace back to the initial session state. This means that any variables that you set during execution of an endpoint are cleared after execution.

One of the caveats with auto-scoped variables is that they do not work with built-in variables such as $_ or $PSItem. You can see that this doesn’t work in a regular PowerShell console.

PS C:\> Set-Variable -Name _ -Value "test"
PS C:\> $_
PS C:\>

Explicit-Scoped Variables

As seen with the caveat of auto-scoped variables, you can see that certain PowerShell constructs don’t behave as you expect in UD.

For example, the below ForEach-Object doesn’t work as expected. The $_ will be $null in the New-UDEndpoint endpoint. This is because the PowerShell engine is resetting the $_ variable after we set it.

1..100 | ForEach-Object {
     New-UDButton -OnClick {
           Show-UDToast -Message $_
     }
 }

To work around this, you can use the New-UDEndpoint cmdlet to specify an argument list to be passed to the endpoint. The reason this works is that when New-UDEndpoint is called, it is still in the same runspace as the ForEach-Object. This means that we still have access to the current value of the $_ variable. We then store that variable value along with the endpoint and make it accessible via the $ArgumentList variable when it’s executed later.

1..100 | ForEach-Object {
    New-UDButton -OnClick (New-UDEndpoint -Endpoint {
        Show-UDToast -Message $ArgumentList[0]
    } -ArgumentList $item)
 }

Cache and Session Scoping

Cache and session scoping allow variables to be stored globally and per user. The Cache scope is considered global to the current instance of UD. If you set a cache variable, it’s available in any endpoint.

Cache scoping works by defining a PS Provider (like the file system provider). When you set a variable using cache scoping, it’s actually doing something like Set-Item on the cache provider. Internally, the cache provider takes advantage of the ASP.NET Core MemoryCache class to store those variables in memory.

Since the memory cache is global and available in all endpoints, accessing those variables work from any endpoint. Depending on the order of operations, you can encounter issues where setting a variable in the cache happens after trying to access it so you may need to initialize the variable ahead of time.

You can access the cache scope outside of the dashboard entirely.

Import-Module UniversalDashboard
$Cache:Init = 'InitMe'

Session scope works a bit differently. It takes advantage of the current user’s session. When a user connects to UD, they are granted an ASP.NET session cookie. This cookie identifies the user’s session. Data that is stored within the session cache can only be set and retrieved by the browser that has that session cookie.

Aside from that, the session scope works much the same as the cache scope.

In Conclusion

Scoping can be a little weird in UD due to the nature of multiple runspaces. Remember that auto-scoping should work in most cases but you can resort to explicit-scoping when necessary. The cache and session scopes can also be used to avoid these types of scoping issues all together but overuse of the session scope can cause race conditions due to the threaded nature of the web server.

Best practice would be to use auto-scoping, where possible. When dealing with loops and automatic variables, such as $_, you should use explicit-scope. When dealing with user state, use the session scope but know that there is no guarantee as to which endpoint will load first. Finally, the cache variable can be used to globally available data.

Posted on Leave a comment

Best Practices for Universal Dashboard Performance

Universal Dashboard is a web framework for PowerShell. It allows you to create websites and REST APIs with just PowerShell script. Unlike other managed languages, like C# and F#, PowerShell is not compiled completely to highly optimized IL code during a compilation step. Instead, it is parsed, tokenized, interpreted and compiled during runtime. This results in a huge difference in the performance of other IL-based languages. In a framework such as Universal Dashboard, this can be especially evident.

In this blog post, we will look at some performance considerations when looking at using Universal Dashboard as your platform for your next web project.

Base Line

The baseline performance for the Universal Dashboard web-server is below. Universal Dashboard is built on ASP.NET Core. Although ASP.NET Core is capable of extremely high requests per second, Universal Dashboard clocks in a bit lower. The reason for this is that each request must allocate a runspace from a runspace pool, set up the runspace for execution, parse and execute a PowerShell Script Block and then serialize any results to JSON.

You can see below that the web server was capable of about 1000 requests per second on a machine with 8 CPU cores. Your results may vary but this should suffice for most low-to-medium traffic internal tools.

PS C:\Users\adamr> Measure-Command { 1..8 | % { Start-ThreadJob { 1..1000 | % { Invoke-WebRequest http://localhost:10004/test  } }  } | Wait-Job }
 
 Days              : 0
 Hours             : 0
 Minutes           : 0
 Seconds           : 8
 Milliseconds      : 432

Performance Tips

Cache Whenever Possible

Most of the time, the performance issues people face with Universal Dashboard have nothing to do with Universal Dashboard. Running PowerShell scripts can be slow. The is especially true when accessing remote resources. Users expect quick response from websites. To aid in this, it’s suggested to utilize Scheduled Endpoints and the Cache scope to avoid having to load resources every time a page loads.

For example, assume that I’m calling a remote REST API to load some resources in an endpoint that then displays them in a grid.

New-UDGrid -Title 'Movies' -Endpoint {
     Invoke-RestMethod http://movieindex.io/api/movies | Out-UDGridData
}

Due to the nature of how Endpoint script blocks work, the above would call the movieindex.io REST API every time the page is loaded. If you have many users accessing your dashboard, this means that you will have many calls to movieindex.io.

In order to improve the performance of this type of component, you can instead load the movie data on an interval and show users cached data. Instead of each user reading directly from the movieindex.io API, they are now reading from the Universal Dashboard Cache scope’s memory.

$Schedule = New-UDEndpointSchedule -Every 10 -Minute
 $Endpoint = New-UDEndpoint -Schedule $Schedule -Endpoint {
     $Cache:Movies = Invoke-RestMethod http://movieindex.io/api/movies
 }
 New-UDGrid -Title 'Movies' -Endpoint {
     $Cache:Movies | Out-UDGridData
 }

You can cache any amount of data you’d like up until you run out of memory on your machine. Be careful with caching large database tables in memory.

Favor Content over Endpoint

Content and Endpoint script blocks can be confusing. The main difference between a Content and an Endpoint script block is that Content is executed at the time the cmdlet is run and an Endpoint script block is executed when a component is loaded on the page.

For example, if we have a New-UDElement with the content below, the script block itself is actually executed when the New-UDElement is called.

New-UDElement -Tag 'div' -Content {
     New-UDElement -Tag 'div' -Content {'I run right away!'}
}

Alternatively, if you use an Endpoint, something different happens. The Endpoint script block is not run when the cmdlet is executed. Instead, it is cached inside the Universal Dashboard Endpoint Service for execution at a later time. Typically, this later time is when the component is loaded on the page.

New-UDElement -Tag 'div' -Endpoint {
     New-UDElement -Tag 'div' -Content {'I run when the page is loaded!'}
}

There is a visible performance difference between these different methods. The first method returns all the data during the first HTTP request from the server. The second method requires a second HTTP method to call back to the server to execute the endpoint script block and return the resulting data.

This can be especially tricky when nesting many Endpoint script blocks together. The below example requires 5 HTTP requests to be made.

New-UDElement -Tag 'div' -Endpoint {
     New-UDElement -Tag 'div' -Endpoint {'I run when the page is loaded!'}
     New-UDElement -Tag 'div' -Endpoint {'I run when the page is loaded!'}
     New-UDElement -Tag 'div' -Endpoint {'I run when the page is loaded!'}
}

The benefit of the Endpoint script block is that it allows for dynamic data and controls to be generated when the page is loaded. This is a huge feature of UD. It’s recommended to wrap the outer most component, where it makes sense, in an endpoint and generate the inner components with the Content script block.

The below example creates an outer most div using the Endpoint script block. This means it will require an HTTP request to load the data for the content of component. The inner components use content so they will not require another HTTP request back to the server. They are still dynamic because they are nested within a dynamic Endpoint script block.

New-UDElement -Tag 'div' -Endpoint {
     $DateTime = Get-Date
     New-UDElement -Tag 'div' -Content {$DateTime.Hour}
     New-UDElement -Tag 'div' -Content {$DateTime.Minute}
     New-UDElement -Tag 'div' -Content {$DateTime.Second}
}

Avoid Overuse of New-UDElement

New-UDElement is a very versatile component that allows you to create any HTML element, hook up event handlers and set attributes. The downside with New-UDElement is it requires a lot of information to be sent from the server to the web browser. With each New-UDElement call, the tag, attributes, and any event handlers need to be communicated to the client machine.

Using purpose-built controls, such as New-UDChart, require only the data to be sent to the client rather than all the HTML information.

The previous version of Universal Dashboard used New-UDElement heavily for many of the standard components. Rather than defining JavaScript components and then sending data via purpose-built cmdlets, cmdlets such as New-UDButton, New-UDFab, and New-UDCollapsible, defined their entire structure and data using New-UDElement. This resulted in the JSON payload sent from the UD server to the browser to be very large.

For example, in Universal Dashboard 2.2.0, creating a basic UDCollapsible with a single collapsible item was 1203 characters.

PS C:\Users\adamr> (New-UDCollapsible -Items { New-UDCollapsibleItem -Title "Test" -Content { } } | ConvertTo-Json -Compress).Length
1203

In Univeral Dashboard 2.4.1, the same command returns a JSON payload of 397 characters.

PS C:\Users\adamr> (New-UDCollapsible -Items { New-UDCollapsibleItem -Title "Test" -Content { } } | ConvertTo-Json -Compress).Length
397

Due to this reason, many of the most common controls have now been built into React components. If you want to build your own React components for Universal Dashboard, check out this repository.

Posted on Leave a comment

PowerShell Universal Dashboard – 2.4.1

Bug Fixes

Bug in UD 2.4: running in IIS locally fails w/ Enterprise w/o license – Reported by bielawb

Error while loading UD 2.4 on raspbian – Reported by DanielSSilva

UDTable size and position are not saved in a UDGridLayout – Reported by adamdriscoll

Cannot type in any textbox that is in the UDGridLayout – Reported by adamdriscoll

New-UDMonitor does not update on 2.4 – Reported by rickyxsosa

New-UDButton -Icon wont show selected icon on the button after starting dashboard – Reported by wsl2001

Warning Message in browser after 2.4.0 deployed – Reported by wsl2001

2.4.0: breaking changes or issue in buttons when passing variables? – Reported by PorreKaj

2.4.0 Grid sort is always descending – Reported by PorreKaj

2.4.0 – Hamburger menu icons vary in size – Reported by PorreKaj

Components are always white in 2.4.0 – Reported by PorreKaj

Posted on 2 Comments

Universal Dashboard – 2.4 Release

After a couple months of development, Universal Dashboard 2.4 is now available on the PowerShell Gallery! This release fixes many issues and implements some really cool features. Check out the notes below to find out more or head over to your favorite command prompt to get the latest.

Install-Module UniversalDashboard -AcceptLicense 

Grid Layout

One issue with dashboards is that it is difficult and time consuming to layout many controls. In UD 2.4, you can now layout controls directly in your web browser. The layout is then output as a JSON string that you can store with your dashboards PS1 file. This reduces the nesting of New-UDRow and New-UDColumn calls and makes it way easier to organize your pages.

You can learn more about the grid layout and how to save your layouts to your dashboards on the doc site.

Material UI Controls

Material UI is the most popular Material Design library for React. It has tons of controls and very customizable. In addition to controls, it provides theming as part of the framework. As we move toward a v3 release of UD, we will be looking at replacing Materialize with Material UI. As we work towards that, we will be bringing in new controls. You can take advantage of some of them right now. Check out the Avatar, Button, Card, Chip, Paper and Typography controls in UD 2.4.

More information can be found on the docs site.

Font Awesome v5

UD2.4 now uses Font Awesome version 5. Previously, it had been using Font Awesome version 4.7. With the introduction of v5, you’ll have access to way more icons. Care has been taken to map existing v4.7 icon names to their v5 counterparts. If you encounter any errors with icons, please open a GitHub issue.

Materialize v1

A change in Chrome introduced a change that caused OnClick event handlers to require two clicks to function. This affected many JavaScript libraries; including Materialize. Because of issues like this, UD2.4 now uses the v1 version of Materialize. This also fixed some issues with date pickers as well as other controls.

Simplified New-UDGrid

New-UDGrid no longer requires passing the Headers and Properties parameters. You can just pass your data to Out-UDGridData. Headers and Properties proved redundant in many scenarios. You can still use Headers and Properties to customize these features.

    New-UDGrid -Title "Processes" -Endpoint {
        Get-Process | Select-Object Name,Path | Out-UDGridData
    }

Improved Custom React Components

Due to enhancements in custom component support, the entire Material UI component library is defined with only JavaScript and PowerShell. There is no need for C# code. This PowerShell to React binding makes it extremely easy to introduce new components. UD can load webpack chunks, CSS and even source maps for custom component libraries.

If you want to see what this looks like, head over to the Universal Dashboard GitHub repository. Full documentation for this feature is in the works and you’ll see many more React controls being brought into the UD ecosystem.

Access to Authorization Policies

You can now access Authorization Policies via Get-UDAuthorizationPolicy. Rather than just limiting which page a user has access to, you can now use this cmdlet in your Endpoints to adjust how the page itself behaves based on that authorization policy. For example, you can check the authorization policy to see if a user is an admin and then hide a control based on if they are not.

Here’s an example of doing just that.

$AuthPolicy = Get-UDAuthorizationPolicy 

if ($AuthPolicy -contains 'AdminPolicy') {
     New-UDHeading -Text "Only Admins See This"
}  

A Word about the Designer

In the 2.4-beta1, the ability to use a basic designer was available. This feature did not make it into the 2.4 release. The designer itself was not very useful in it’s current implementation so it was decided to instead implement the grid layout and work some more on the designer for a future release. Working longer on the designer would have delayed the 2.4 build that had a lot of much needed fixes.

Documentation

Documentation has been updated for the 2.4 release. Additionally, documentation for the 2.3 release is still available. Instead of continuing a single stream of docs, we will now be taking advantage of GitBook Releases. Beginning with the 2.3 release, this will allow for you to access older versions of documentation specific to the UD version you are using.

You can select the doc version by clicking the drop down in the top left navigation menu of the docs site.

And Much More

Check out the full list of release notes here.

Posted on Leave a comment

Universal Dashboard – 2.4 Beta 1 – Dashboard Designer, Material UI Components and more!

Today, we’ve released PowerShell Universal Dashboard v2.4.0-beta1. You can download the latest from the PowerShell Gallery. There are tons of awesome features and bug fixes.

Install-Module UniversalDashboard -AllowPrerelease

Features

Universal Dashboard Designer

One of the first feature requests I received for Universal Dashboard has now started to take shape. The Universal Dashboard Designer is beginning life as a drop and drop page creator. You can select from components, edit their properties and drag and drop them on the design surface. A script is generated in the background that will run in both Enterprise and Community. There is no need to write PowerShell script by hand to layout your dashboards with the UD Designer.

There is a hosted demo version of the designer available for you to play with now.

Hosted Demo Site

Give it a shot and provide us with feedback. If you install the UniversalDashboard module, you can run the designer locally be using Start-UDPageDesigner.

For a more complete run down on how to use the designer, take a look at my YouTube channel.

Material UI Components

In an effort to provide even more options to Universal Dashboard users, we have decided to start to migrate from the Materialize library to the Material UI library. In addition to having many more options, the library is also built on React so it integrates much easier into Universal Dashboard. Thanks to Alon Gvili, there are now 10 of the Material UI controls available in UD.

  • Avatar
  • Button
  • Card
  • Chip
  • Icon Button
  • Link
  • List
  • Paper
  • PDF
  • Typography

For example, if you’ve been using the existing New-UDCard, you’ll be happy to find the New-UDMUCard provides many more options for creating exceedingly customizable and beautiful widgets for your dashboards.

To see how this example was produced, visit the UD GitHub page.

Grid Layout

The UD designer takes advantage of the the New-UDGridLayout command to organize components on the page. Unlike New-UDRow and New-UDColumn you don’t nest the layout and controls directly. The layout is defined via Hashtables or JSON. The leads to cleaner scripts that don’t nest deeply with scriptblocks. Additionally, you can provide the ability for users to drag and drop components themselves. Their state is saved to local browser storage so, whenever they load the dashboard, they have the same layout.

Simplified Custom React Components

Due to enhancements in custom component support, the entire Material UI component library is defined with only JavaScript and PowerShell. There is no need for C# code. This PowerShell to React binding makes it extremely easy to introduce new components. UD can load webpack chunks, CSS and even source maps for custom component libraries.

If you want to see what this looks like, head over to the Universal Dashboard GitHub repository. Full documentation for this feature is in the works and you’ll see many more React controls being brought into the UD ecosystem.

Access to Authorization Policies

You can now access Authorization Policies via Get-UDAuthorizationPolicy. Rather than just limiting which page a user has access to, you can now use this cmdlet in your Endpoints to adjust how the page itself behaves based on that authorization policy. For example, you can check the authorization policy to see if a user is an admin and then hide a control based on if they are not.

Here’s an example of doing just that.

 $AuthPolicy = Get-UDAuthorizationPolicy 

if ($AuthPolicy -contains 'AdminPolicy') {
     New-UDHeading -Text "Only Admins See This"
}  

For a full list of issues, please visit GitHub.