UniversalDashboard.Forge.psm1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
function New-UDDesktopApp {
    <#
    .SYNOPSIS
    Generate a desktop app with Universal Dashboard
 
    .DESCRIPTION
    Generates a desktop application with Universal Dashboard using electron.
 
    .PARAMETER Path
    The path to the dashboard.ps1 file you want to create an app out of.
 
    .PARAMETER Name
    The name of the electron application.
 
    .PARAMETER OutputPath
    The output path for the application.
 
    .PARAMETER IconUrl
    A web URL to an .ICO file to use as the icon displayed in "Programs and Features."
 
    If not specified, the Atom logo will be used.
 
    .PARAMETER SetupIcon
    The .ICO file used as the icon on the generated install file.
 
    Must be a path to the file on a local disk.
 
    .PARAMETER LoadingGif
    The local path to a .GIF to be displayed as a splash while installing your generated application.
 
    .EXAMPLE
    New-UDDesktopApp -Path "./dashboard.ps1" -OutputPath "./out" -Name "MyApp"
 
    .NOTES
    This cmdlet requires NodeJS to be installed.
    #>


    param(
        [Parameter(Mandatory)]
        $Path,
        [Parameter(Mandatory)]
        $Name,
        [Parameter()]
        $OutputPath,
        [Parameter()]
        [ValidateSet("pwsh", "powershell")]
        $PowerShellHost = (Get-Process -Id $Pid).ProcessName,
        [Parameter()]
        $IconUrl,
        [Parameter()]
        $SetupIcon,
        [Parameter()]
        $LoadingGif
    )

    End {
        $provider = $null;
        $drive = $null
        $pathHelper = $ExecutionContext.SessionState.Path
        $Path = $pathHelper.GetUnresolvedProviderPathFromPSPath($Path, [ref]$provider, [ref]$drive)
        $PathInfo = Get-Item $Path

        if ($PathInfo.PSIsContainer)
        {
            Write-Verbose "Path is a directory. Locating dashboard.ps1"
            $dashboard = Join-Path $PathInfo.FullName "dashboard.ps1"
            if (-not (Test-Path $dashboard))
            {
                throw "No dashboard.ps1 found in $Path"
            }
        }
        else
        {
            $dashboard = $Path
        }

        $Npx = Get-Command npx
        if ($null -eq $Npx)
        {
            throw "NodeJS is required to run New-UDDesktopApp. Download here: https://nodejs.org"
        }

        $Git = Get-Command git
        if ($null -eq $Git)
        {
            throw "Git is required to run New-UDDesktopApp."
        }

        if ($null -eq $OutputPath)
        {
            $OutputPath = $PSScriptRoot
            Write-Verbose "No output path specified. Using: $OutputPath"
        }
        else
        {
            $provider = $null;
            $drive = $null
            $pathHelper = $ExecutionContext.SessionState.Path
            $OutputPath = $pathHelper.GetUnresolvedProviderPathFromPSPath($OutputPath, [ref]$provider, [ref]$drive)
            Write-Verbose "Output path resolved to: $OutputPath"
        }

        if (Test-Path (Join-Path $OutputPath $Name))
        {
            Write-Verbose "Output path exists. Removing existing output path."
            Remove-Item (Join-Path $OutputPath $Name) -Force -Recurse
        }

        if (-not (Test-Path $OutputPath))
        {
            Write-Verbose "Output path does not exist. Creating new output path."
            New-Item -Path $OutputPath -ItemType Directory | Out-Null
        }

        Push-Location $OutputPath
        Write-Verbose "Creating electron app $Name"
        npm install create-electron-app@latest --global
        npx create-electron-app $Name
        Pop-Location

        $src = [IO.Path]::Combine($OutputPath, $Name, 'src')

        if ($PathInfo.PSIsContainer)
        {
            Write-Verbose "Copying contents of $Path to $src"
            Copy-Item -Path "$($PathInfo.FullName)/*" -Destination $src -Exclude (Split-Path $OutputPath -Leaf) -Recurse
        }

        Write-Verbose "Copying dashboard and index.js to electron src folder: $src"

        $content = Get-Content $dashboard -Raw
        $content = @'
{0} = {0} + ";{1}"
Import-Module UniversalDashboard
 
{2}
'@
 -f '$Env:PSModulePath', '$PSScriptRoot', $content

        $content | Out-File (Join-Path $src "dashboard.ps1") -Force -Encoding utf8

        Copy-Item -Path (Join-Path $PSScriptRoot "index.js" ) -Destination $src -Force
        $indexJs = Join-Path $src "index.js"

        $port = Get-PortNumber -Path $dashboard
        Set-ForgeVariable -IndexPath $indexJs -PowerShellHost $PowerShellHost -Port $port

        $packageConfig = [IO.Path]::Combine($OutputPath, $Name, 'package.json')
        $squirrelSplat = @{'ConfigPath' = $packageConfig}
        if ($IconUrl) {$squirrelSplat['IconUrl'] = $IconUrl}
        if ($SetupIcon) {
            $iconPath = (Get-ChildItem -Path $src -Filter (Split-Path $SetupIcon -Leaf) -Recurse)[0].FullName
            $squirrelSplat['SetupIcon'] = $iconPath
        }
        if ($LoadingGif) {
            $gifPath = (Get-ChildItem -Path $src -Filter (Split-Path $LoadingGif -Leaf) -Recurse)[0].FullName
            $squirrelSplat['LoadingGif'] = $gifPath
        }
        Set-SquirrelConfig @squirrelSplat

        Write-Verbose "Copying Universal Dashboard to output path"
        Copy-UniversalDashboard -OutputPath $src

        Write-Verbose "Building electron app with forge"
        npm i -g @electron-forge/cli
        Set-Location (Join-Path $OutputPath $Name)
        electron-forge make
    }
}

function Copy-UniversalDashboard {
    param($OutputPath)

    $UniversalDashboard = Get-InstalledModule -Name UniversalDashboard -ErrorAction SilentlyContinue

    if ($null -eq $UniversalDashboard)
    {
        $UniversalDashboard = Get-InstalledModule -Name UniversalDashboard.Community -ErrorAction SilentlyContinue
        if ($null -eq $UniversalDashboard)
        {
            throw "You need to install UniversalDashboard or UniversalDashboard.Community using Install-Module."
        }
    }

    $Directory = $UniversalDashboard.InstalledLocation

    $UDDirectory = Join-Path $OutputPath "UniversalDashboard"
    New-Item $UDDirectory -ItemType Directory | Out-Null

    Copy-Item $Directory -Recurse -Destination $UDDirectory
}

function Set-ForgeVariable {
    param(
        $IndexPath,
        $PowerShellHost,
        $Port
    )

    $content = Get-Content -Path $IndexPath -Raw

    Write-Verbose "Setting ForgeVariable PowerShellHost: $PowerShellHost"
    $content = $content.Replace('$PowerShellHost', $PowerShellHost)

    Write-Verbose "Setting ForgeVariable Port: $Port"
    $content = $content.Replace('$Port', $Port)

    $content | Out-File -FilePath $IndexPath -Force -Encoding utf8
}

function Get-PortNumber {
    param (
        $Path
    )

    $content = Get-Content -Path $Path
    $match = [regex]::Match($content, '[sS]tart-[uUdD]{3}ash.+-Port (\d+)')

    if ($match.Success) { $match.Groups[1].Value } else { 80 }
}

function Set-SquirrelConfig {
    param(
        [Parameter(Mandatory)]
        $ConfigPath,
        $IconUrl,
        $SetupIcon,
        $LoadingGif
    )

    $content = Get-Content -Path $ConfigPath -Raw | ConvertFrom-Json
    $squirrelConfig = $Content.Config.Forge.Makers.Where({$_.Name -like '*squirrel'}).Config
    $keys = $PSBoundParameters.Keys.Where({$_ -ne 'ConfigPath'})

    foreach ($parameter in $keys) {
        Write-Verbose ('Setting SquirrelConfig {0}: {1}' -f $parameter, $PSBoundParameters[$parameter])
        $name = $parameter -replace '^\w', $parameter.Substring(0, 1).ToLower()
        $squirrelConfig | Add-Member -MemberType NoteProperty -Name $name -Value $PSBoundParameters[$parameter]
    }

    $Content | ConvertTo-Json -Depth 10 | Out-File -FilePath $ConfigPath -Force -Encoding utf8
}