|
<# |
|
.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 |
|
} |
|
} |