Skip to content

Instantly share code, notes, and snippets.

@JustinGrote
Last active September 7, 2023 10:48
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save JustinGrote/b1653aa0acc5959f68e6aab7f82bece6 to your computer and use it in GitHub Desktop.
Save JustinGrote/b1653aa0acc5959f68e6aab7f82bece6 to your computer and use it in GitHub Desktop.
Powershell Universal BootStrap

Powershell Universal Dashboard Bootstrap

This script will get you up and running quickly with Universal Dashboard, and create per-folder data instances so you can rapidly prototype different dashboards

Quick Start

  1. Open an empty folder or a folder with dashboard .ps1 files.
  2. Run iex (iwr -useb git.io/BPU)
  3. That's it! Dashboard will autostart and open in your browser Note: If you are on Powershell 6+ you can omit the -useb

Capture

Make a self-executable dashboard

You can make a self-executable dashboard by adding this line to the dashboard configuration if (-not $EndpointService) {& ([ScriptBlock]::Create((iwr git.io/BPU))) -reset;return}

Capture

Advanced Usage

Several parameters are supported, it's recommend to bring down the bootstrap as a function for ease of use:

New-Item function:/Start-UD -Value ([String](iwr git.io/BPU)) -force

help Start-UD

<#
.SYNOPSIS
Bootstraps the Powershell Universal Dashboard with per-folder configurations on a random port
.DESCRIPTION
Saves a copy of Universal Dashboard to the LocalAppData/UniversalDashboard folder and starts it for the current folder.
This is useful for rapid testing, or integrating into scripts for easy deployment
.EXAMPLE
Start-UD -DashboardVersion 1.2.9
Bootstraps version 1.2.9 of the dashboard specifically
.EXAMPLE
Start-UD -Wait -NoStart
Starts Universal Dashboard but does not open in a separate process, and does not open a separate browser window
.EXAMPLE
Start-UD -Port 5555
Starts Universal Dashboard on a consistent port
.EXAMPLE
Start-UD -Reset
"Factory Reset" of universal dashboard, wiping all metadata for the current folder.
.EXAMPLE
Start-UD -Reinstall
Redownloads Universal Dashboard to the local AppData folder, use this command to upgrade to new versions as well
#>
#requires -version 5.1
using namespace System.Management.Automation.Language
[CmdletBinding()]
param (
#Specify one or more dashboards to load. By default it will load all dashboard files in the current directory (does not recurse)
[String[]]$Path = $(Get-ChildItem "*.ps1"),
#Version of the dashboard to download. Defaults to latest available version.
[Version]$DashboardVersion = $([string](Invoke-WebRequest -UseBasicParsing 'https://imsreleases.blob.core.windows.net/universal/production/version.txt')),
#TCP Port to start the dashboard on. Defaults to a random port between 5000 and 5999
[Int]$Port = $(get-random -min 5000 -max 5999),
#Don't split out Universal Dashboard to another process
[Switch]$Wait,
#Factory reset the dashboard settings
[Switch]$Reset,
#Delete Everything
[Switch]$Reinstall,
#Don't open web browser links for each dashboard
[Switch]$NoStart,
#Your current operating system. This is autodetected as either 'win' or 'linux'
[String]$OS = $(if ($isLinux) {'linux'} else {'win'}),
#URI of the dashboard version you wish to download. You do not normally need to change this
[String]$DashboardDownloadPath = "https://imsreleases.blob.core.windows.net/universal/production/$dashboardVersion/Universal.$OS-x64.$dashboardVersion.zip",
#Where Universal Dashboard should be installed. Defaults to LocalAppData/UniversalDashboard
[String]$DashboardDestination = (Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) "UniversalDashboard"),
#Path where the Dashboard zip should be downloaded
[String]$DashboardTempZipPath = "${DashboardDestination}.zip",
#Path to where settings should be stored. Defaults to .universal folder in current directory
[String]$SettingsPath = "$PWD/.universal",
#Path to where Universal Dashboard data (database, dashboard templates, etc.) should be stored. Defaults to data folder in the settings directory
[String]$DataPath = "$SettingsPath/data",
#Path to the dashboard .ps1 files. Defaults to current directory
[String]$RepositoryPath = $PWD,
#When to timeout if there is no progress in starting universal dashboard, in milliseconds
[Int]$Timeout = 10000
)
$ErrorActionPreference = 'Stop'
if ($UDExecutionService) {
throw 'BOOM'
Write-Debug "Universal Dashboard is running. Exiting Bootstrap."
return
}
#Clear logs for log watching
try {
$LogPath = "$DataPath/log-*.txt"
if (Test-Path $LogPath) {
Get-Item "$DataPath/log-*.txt" | Foreach {Rename-Item -Path $PSItem.fullname -NewName "$($PSItem.fullname).old"}
}
} catch [IO.IOException] {
throw 'Universal Dashboard is already running here, please end it or specify a different SettingsPath with the -SettingsPath parameter'
}
if ($Reset -and (Test-Path $SettingsPath)) {Remove-Item $SettingsPath -Force -ErrorAction stop}
if ($Reinstall -and (Test-Path $DashboardDestination)) {Remove-Item $DashboardDestination -Force -ErrorAction stop}
if ($Reinstall -and (Test-Path $DashboardTempZipPath)) {Remove-Item $DashboardTempZipPath -Force -ErrorAction stop}
#TODO: Extract from memory using a stream
$UniversalServerPath = (Join-Path $DashboardDestination 'Universal.Server.Exe')
$DashboardVersionPath = "$DashboardDestination/version.txt"
if (-not (Test-Path $UniversalServerPath)) {
Write-Host -Fore Green "Downloading Universal Dashboard $DashboardVersion from $DashboardDownloadPath"
[void]([Net.WebClient]::new()).DownloadFile($DashboardDownloadPath,$DashboardTempZipPath)
[void](New-Item -ItemType Directory -Force -ErrorAction SilentlyContinue $DashboardDestination)
[void](Expand-Archive -Path $DashboardTempZipPath -DestinationPath $DashboardDestination -Force)
$DashboardVersion.ToString(3) > $DashboardVersionPath
[void](Get-ChildItem $DashboardDestination -Recurse | Unblock-File)
if ($isLinux) {chmod +x $UniversalServerPath}
}
if (-not (Test-Path $DashboardVersionPath)) {
Write-Warning "$DashboardVersionPath not found, cannot detect version change. Consider using -reinstall"
} elseif ($DashboardVersion -gt ([Version](Get-Content -Raw $DashboardVersionPath))) {
Write-Host -fore Yellow "A new version $DashboardVersion of Universal Dashboard is available!"
Write-Host -fore Yellow "To Upgrade: & ([ScriptBlock]::Create((iwr git.io/BPU))) -reinstall"
}
$Data = New-Item -Path $DataPath -ItemType Directory -Force -ErrorAction Stop
$udSettings = @{
Kestrel__Endpoints__HTTP__URL = "http://*:$Port"
Logging__Path = Join-Path $Data 'log.txt'
Data__RepositoryPath = $RepositoryPath
Data__ConnectionString = Join-Path $Data 'database.db'
UniversalDashboard__AssetsFolder = Join-Path $Data 'Dashboard'
}
$udSettings.keys.foreach{
Set-Item -Path "Env:$PSItem" -Value $udSettings.$PSItem
}
try {
$dashboards = Get-Content $RepositoryPath/.universal/dashboards.ps1 -ErrorAction Stop
} catch {
New-Item -Force $RepositoryPath/.universal/dashboards.ps1 > $null
}
#Load Modules
$UDModulePath = Join-Path $DashboardDestination 'Cmdlets'
Import-Module -Force (Join-Path $UDModulePath "Universal.psd1")
$dashboardInfo = foreach ($scriptItem in $Path) {
$parsedFile = [Parser]::ParseFile(
(Resolve-Path $scriptItem), #Input
[ref]$null, #tokens
[ref]$null #errors
)
#Consider it a dashboard if the New-UDDashboard function is used in the script
$dashboardCommand = $parsedfile.findall({$x = $args[0];$x -is [CommandAst] -and $x.CommandElements[0].Value -eq 'New-UDDashboard'},$false)
if ($dashboardCommand) {
$elements = $dashboardCommand.CommandElements
#TODO: Better way to find the index
[int]$i=0
[bool]$skiprest = $false
[int]$TitleIndex = $elements | ForEach-Object {
if (-not $skipRest) {
if ($PSItem.ParameterName -eq 'Title') {$i;$skipRest=$true}
$i++
}
}
if (-not $titleIndex) {throw "${scriptItem}: The New-UDDashboard command does not have a -Title parameter"}
$Title = $elements[([int]$titleIndex)+1]
if ($Title.StaticType.Name -ne 'String') {throw "${scriptItem}: The title must be a static string for this bootstrap to work."}
$DashboardName = $Title.Value
$DashboardUri = $DashboardName -replace '[^\w-]',''
$ScriptPath = (Resolve-Path $scriptItem -Relative) -replace '^\.[\\/]',''
#TODO: AST-based handling of this
if ($dashboards -match "-Name `"$DashboardName`"") {
Write-Host -ForegroundColor DarkCyan "Exists in config: Dashboard $DashboardName - $scriptPath"
} else {
$dashboards += "New-PSUDashboard -Name `"$DashboardName`" -FilePath `"$scriptPath`" -BaseUrl `"/$DashboardUri`" -Framework `"UniversalDashboard:3.0.0-beta7`""
Write-Host -ForegroundColor Cyan "Added Dashboard $DashboardName at '/$DashboardUri' - $scriptPath"
}
[PSCustomObject]@{
Name = $DashboardName
URI = "http://localhost:$Port/$DashboardUri/home"
}
} else {
Write-Debug "$ScriptItem is not a dashboard"
}
}
$dashboards | Out-File -FilePath $SettingsPath/dashboards.ps1
Write-Host -Fore Green "Starting Universal Dashboard at http://$($ENV:COMPUTERNAME):$Port"
Write-Host -Fore Cyan ($dashboardInfo | Format-Table | out-string)
if ($Wait) {
try {
& $UniversalServerPath
} catch {throw} finally {
$udSettings.keys.foreach{
Remove-Item -Path "Env:$PSItem"
}
}
} else {
Start-Process $UniversalServerPath
Write-Host -NoNewLine -Fore DarkCyan "Waiting for Dashboard startup..."
try {
$watcher = [IO.FileSystemWatcher]::new($dataPath,'log-*.txt')
while ($true) {
$result = $watcher.WaitForChanged('All',$Timeout)
if ($result.TimedOut) {throw 'Universal Automation did not start up in the expected timeframe. Check for configuration errors'}
if (Get-Content -Raw -Path (Join-Path $DataPath $result.name) | Select-String 'Application Started' -Quiet) {
break
}
}
} catch {throw} finally {
$watcher.dispose()
}
Write-Host -Fore Green 'OK!'
}
if (-not $Wait -and -not $NoStart) {
$dashboardinfo.uri.foreach{
Start-Process $PSItem
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment