bcdocker 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +224 -0
- package/dist/executor.d.ts +7 -0
- package/dist/executor.js +72 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +203 -0
- package/package.json +59 -0
- package/ps/Apps.ps1 +360 -0
- package/ps/BCDocker.psm1 +30 -0
- package/ps/Container.ps1 +397 -0
- package/ps/Helpers.ps1 +505 -0
- package/ps/Tests.ps1 +144 -0
package/ps/Container.ps1
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
function New-BCDContainer {
|
|
2
|
+
<#
|
|
3
|
+
.SYNOPSIS
|
|
4
|
+
Creates a new Business Central Docker container.
|
|
5
|
+
|
|
6
|
+
.PARAMETER ContainerName
|
|
7
|
+
Name for the Docker container. Default: "bcsandbox"
|
|
8
|
+
|
|
9
|
+
.PARAMETER Version
|
|
10
|
+
BC artifact type: "sandbox", "onprem", or a specific version like "26.0". Default: "sandbox"
|
|
11
|
+
|
|
12
|
+
.PARAMETER Country
|
|
13
|
+
Localization code (us, w1, gb, nl, dk, etc.). Default: "us"
|
|
14
|
+
|
|
15
|
+
.PARAMETER UserName
|
|
16
|
+
Container admin username. Default: "admin"
|
|
17
|
+
|
|
18
|
+
.PARAMETER Password
|
|
19
|
+
Container admin password. Default: "P@ssw0rd!"
|
|
20
|
+
|
|
21
|
+
.PARAMETER Credential
|
|
22
|
+
PSCredential object. Overrides UserName/Password if supplied.
|
|
23
|
+
|
|
24
|
+
.PARAMETER IncludeTestToolkit
|
|
25
|
+
Include the BC Test Toolkit. Default: $true
|
|
26
|
+
|
|
27
|
+
.PARAMETER TestLibrariesOnly
|
|
28
|
+
Install only test libraries (faster). Default: $false
|
|
29
|
+
|
|
30
|
+
.PARAMETER MemoryLimit
|
|
31
|
+
Container memory limit. Default: "8G"
|
|
32
|
+
|
|
33
|
+
.PARAMETER Isolation
|
|
34
|
+
Isolation mode: hyperv or process. Default: "hyperv"
|
|
35
|
+
|
|
36
|
+
.PARAMETER LicenseFile
|
|
37
|
+
Path or URL to a .bclicense / .flf file.
|
|
38
|
+
|
|
39
|
+
.PARAMETER BypassCDN
|
|
40
|
+
Skip Azure CDN and use blob storage directly.
|
|
41
|
+
#>
|
|
42
|
+
[CmdletBinding()]
|
|
43
|
+
param(
|
|
44
|
+
[string]$ContainerName = "bcsandbox",
|
|
45
|
+
[string]$Version = "sandbox",
|
|
46
|
+
[string]$Country = "us",
|
|
47
|
+
[string]$UserName = "admin",
|
|
48
|
+
[string]$Password = "P@ssw0rd!",
|
|
49
|
+
[PSCredential]$Credential,
|
|
50
|
+
[bool]$IncludeTestToolkit = $true,
|
|
51
|
+
[bool]$TestLibrariesOnly = $false,
|
|
52
|
+
[bool]$IncludePerformanceToolkit = $false,
|
|
53
|
+
[string]$MemoryLimit = "8G",
|
|
54
|
+
[ValidateSet("hyperv","process")]
|
|
55
|
+
[string]$Isolation = "hyperv",
|
|
56
|
+
[string]$LicenseFile = "",
|
|
57
|
+
[switch]$BypassCDN
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
Write-BCBanner "Create BC Container"
|
|
61
|
+
|
|
62
|
+
# Step 1 — TLS & Docker
|
|
63
|
+
Write-BCStep "1/8" "Enforcing TLS 1.2..."
|
|
64
|
+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
65
|
+
|
|
66
|
+
if (-not (Assert-DockerReady)) { return }
|
|
67
|
+
|
|
68
|
+
$osBuild = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").CurrentBuild
|
|
69
|
+
Write-BCInfo "Host OS Build: $osBuild"
|
|
70
|
+
if ([int]$osBuild -ge 26200) {
|
|
71
|
+
Write-Host " Windows 11 25H2+ detected - CDN workarounds active." -ForegroundColor Yellow
|
|
72
|
+
if (-not $BypassCDN) {
|
|
73
|
+
Write-BCInfo "Tip: If downloads fail, re-run with -BypassCDN"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Step 2 — BcContainerHelper
|
|
78
|
+
Write-BCStep "2/8" "Checking BcContainerHelper..."
|
|
79
|
+
if (-not (Assert-BcContainerHelper)) { return }
|
|
80
|
+
$helperVersion = (Get-Module BcContainerHelper).Version
|
|
81
|
+
Write-BCSuccess "BcContainerHelper v$helperVersion"
|
|
82
|
+
|
|
83
|
+
# Step 3 — Credentials
|
|
84
|
+
Write-BCStep "3/8" "Preparing credentials..."
|
|
85
|
+
if (-not $Credential) {
|
|
86
|
+
$Credential = Get-BCCredential -UserName $UserName -Password $Password
|
|
87
|
+
}
|
|
88
|
+
Write-BCInfo "User: $($Credential.UserName)"
|
|
89
|
+
|
|
90
|
+
# Step 4 — Resolve artifact URL
|
|
91
|
+
Write-BCStep "4/8" "Resolving artifacts (type=$Version, country=$Country)..."
|
|
92
|
+
$artifactUrl = Get-BcArtifactUrl -type $Version -country $Country -select Latest
|
|
93
|
+
if (-not $artifactUrl) {
|
|
94
|
+
Write-BCError "Failed to resolve artifact URL for version='$Version', country='$Country'."
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
Write-BCSuccess "Resolved: $artifactUrl"
|
|
98
|
+
|
|
99
|
+
if ($BypassCDN) {
|
|
100
|
+
$artifactUrl = $artifactUrl.Replace("bcartifacts.azureedge.net", "bcartifacts.blob.core.windows.net")
|
|
101
|
+
Write-BCInfo "CDN bypass: using blob storage directly"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Step 5 — Connectivity test
|
|
105
|
+
Write-BCStep "5/8" "Testing connectivity..."
|
|
106
|
+
foreach ($endpoint in @("bcartifacts.azureedge.net", "bcartifacts.blob.core.windows.net")) {
|
|
107
|
+
try {
|
|
108
|
+
$tcp = Test-NetConnection -ComputerName $endpoint -Port 443 `
|
|
109
|
+
-WarningAction SilentlyContinue -ErrorAction SilentlyContinue
|
|
110
|
+
$status = if ($tcp.TcpTestSucceeded) { "OK" } else { "BLOCKED" }
|
|
111
|
+
$color = if ($tcp.TcpTestSucceeded) { "Green" } else { "Red" }
|
|
112
|
+
Write-Host " ${endpoint}:443 => $status" -ForegroundColor $color
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
Write-Host " ${endpoint}:443 => UNREACHABLE" -ForegroundColor Red
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# Step 6 — Cache cleanup
|
|
120
|
+
Write-BCStep "6/8" "Preparing cache..."
|
|
121
|
+
$versionMatch = [regex]::Match($artifactUrl, '/(sandbox|onprem)/([\d.]+)/')
|
|
122
|
+
if ($versionMatch.Success) {
|
|
123
|
+
$artifactType = $versionMatch.Groups[1].Value
|
|
124
|
+
$majorVersion = $versionMatch.Groups[2].Value.Split('.')[0]
|
|
125
|
+
$cachePath = "C:\bcartifacts.cache\$artifactType"
|
|
126
|
+
|
|
127
|
+
if (Test-Path $cachePath) {
|
|
128
|
+
$staleEntries = Get-ChildItem $cachePath -Directory -ErrorAction SilentlyContinue |
|
|
129
|
+
Where-Object { $_.Name -like "$majorVersion*" }
|
|
130
|
+
if ($staleEntries) {
|
|
131
|
+
Write-BCInfo "Clearing $($staleEntries.Count) cached v$majorVersion artifact(s)..."
|
|
132
|
+
$staleEntries | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
$tempCleared = 0
|
|
138
|
+
Get-ChildItem "$env:LOCALAPPDATA\Temp" -Filter "*.zip" -ErrorAction SilentlyContinue |
|
|
139
|
+
Where-Object { $_.Length -eq 0 -or $_.Name -like "bcContainerHelper*" } |
|
|
140
|
+
ForEach-Object {
|
|
141
|
+
Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue
|
|
142
|
+
$tempCleared++
|
|
143
|
+
}
|
|
144
|
+
if ($tempCleared -gt 0) {
|
|
145
|
+
Write-BCInfo "Cleared $tempCleared stale temp file(s)."
|
|
146
|
+
}
|
|
147
|
+
Write-BCSuccess "Cache ready."
|
|
148
|
+
|
|
149
|
+
# Step 7 — Download artifacts
|
|
150
|
+
Write-BCStep "7/8" "Downloading artifacts (may take 5-15 min on first run)..."
|
|
151
|
+
try {
|
|
152
|
+
Download-Artifacts -artifactUrl $artifactUrl -includePlatform
|
|
153
|
+
Write-BCSuccess "Artifacts cached."
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
if (-not $BypassCDN) {
|
|
157
|
+
Write-BCInfo "Retrying with blob storage fallback..."
|
|
158
|
+
$artifactUrl = $artifactUrl.Replace("bcartifacts.azureedge.net", "bcartifacts.blob.core.windows.net")
|
|
159
|
+
try {
|
|
160
|
+
Download-Artifacts -artifactUrl $artifactUrl -includePlatform
|
|
161
|
+
Write-BCSuccess "Artifacts downloaded via blob storage."
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
Write-BCError "Download failed from both CDN and blob storage."
|
|
165
|
+
Write-BCError $_.Exception.Message
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
Write-BCError "Download failed: $($_.Exception.Message)"
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
# Step 8 — Create container
|
|
176
|
+
$existing = docker ps -a --filter "name=^/${ContainerName}$" --format '{{.Names}}' 2>$null
|
|
177
|
+
if ($existing -eq $ContainerName) {
|
|
178
|
+
Write-BCInfo "Removing existing container '$ContainerName'..."
|
|
179
|
+
Remove-BcContainer -containerName $ContainerName
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
Write-BCStep "8/8" "Creating container '$ContainerName'..."
|
|
183
|
+
if ($IncludeTestToolkit) {
|
|
184
|
+
if ($TestLibrariesOnly) {
|
|
185
|
+
Write-BCInfo "Test Toolkit: Libraries only (Assert, Library-Sales, etc.)"
|
|
186
|
+
} else {
|
|
187
|
+
Write-BCInfo "Test Toolkit: FULL - all Microsoft test framework apps + test codeunits"
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
$params = @{
|
|
191
|
+
containerName = $ContainerName
|
|
192
|
+
artifactUrl = $artifactUrl
|
|
193
|
+
auth = "UserPassword"
|
|
194
|
+
credential = $Credential
|
|
195
|
+
memoryLimit = $MemoryLimit
|
|
196
|
+
accept_eula = $true
|
|
197
|
+
accept_insiderEula = $true
|
|
198
|
+
updateHosts = $true
|
|
199
|
+
includeTestToolkit = $IncludeTestToolkit
|
|
200
|
+
includeTestLibrariesOnly = $TestLibrariesOnly
|
|
201
|
+
includePerformanceToolkit = $IncludePerformanceToolkit
|
|
202
|
+
dns = "8.8.8.8"
|
|
203
|
+
isolation = $Isolation
|
|
204
|
+
enableTaskScheduler = $false
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if ($LicenseFile -and ((Test-Path $LicenseFile -ErrorAction SilentlyContinue) -or ($LicenseFile -match "^https?://"))) {
|
|
208
|
+
$params.licenseFile = $LicenseFile
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
New-BcContainer @params
|
|
212
|
+
|
|
213
|
+
Write-BCBanner "Container Created Successfully" "Green"
|
|
214
|
+
|
|
215
|
+
$bcVersion = Get-BcContainerNavVersion -containerNameOrId $ContainerName
|
|
216
|
+
$webclientUrl = Get-BcContainerUrl -containerName $ContainerName -useHttps:$false
|
|
217
|
+
$testMode = if (-not $IncludeTestToolkit) { "None" }
|
|
218
|
+
elseif ($TestLibrariesOnly) { "Libraries only" }
|
|
219
|
+
else { "Full test framework apps" }
|
|
220
|
+
|
|
221
|
+
Write-BCProperty "Container" $ContainerName
|
|
222
|
+
Write-BCProperty "BC Version" $bcVersion
|
|
223
|
+
Write-BCProperty "Isolation" $Isolation
|
|
224
|
+
Write-BCProperty "Credentials" "$($Credential.UserName) / ********"
|
|
225
|
+
Write-BCProperty "Test Toolkit" $testMode
|
|
226
|
+
Write-Host ""
|
|
227
|
+
Write-BCProperty "Web Client" $webclientUrl "Cyan"
|
|
228
|
+
Write-BCProperty "SOAP" "http://${ContainerName}:7047/BC/WS" "Cyan"
|
|
229
|
+
Write-BCProperty "OData/API" "http://${ContainerName}:7048/BC/api" "Cyan"
|
|
230
|
+
Write-BCProperty "Dev Service" "http://${ContainerName}:7049/BC" "Cyan"
|
|
231
|
+
Write-Host ""
|
|
232
|
+
Write-Host " Quick commands:" -ForegroundColor Gray
|
|
233
|
+
Write-Host " Open browser : Start-Process `"$webclientUrl`"" -ForegroundColor White
|
|
234
|
+
Write-Host " Run tests : Invoke-BCDTests -ContainerName `"$ContainerName`"" -ForegroundColor White
|
|
235
|
+
Write-Host " Remove container : Remove-BCDContainer -ContainerName `"$ContainerName`"" -ForegroundColor White
|
|
236
|
+
Write-Host ""
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function Remove-BCDContainer {
|
|
240
|
+
<#
|
|
241
|
+
.SYNOPSIS
|
|
242
|
+
Removes one or more BC Docker containers via interactive selection.
|
|
243
|
+
#>
|
|
244
|
+
[CmdletBinding()]
|
|
245
|
+
param([string]$ContainerName)
|
|
246
|
+
|
|
247
|
+
if (-not (Assert-BcContainerHelper)) { return }
|
|
248
|
+
|
|
249
|
+
if ($ContainerName) {
|
|
250
|
+
Write-BCBanner "Remove Container"
|
|
251
|
+
Remove-BcContainer -containerName $ContainerName
|
|
252
|
+
Write-BCSuccess "Container '$ContainerName' removed."
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
$selected = Select-BCContainer -Title "Select container(s) to remove" -AllowMultiple
|
|
257
|
+
if (-not $selected) { return }
|
|
258
|
+
|
|
259
|
+
Write-BCBanner "Remove Containers"
|
|
260
|
+
foreach ($name in $selected) {
|
|
261
|
+
Write-BCStep "DEL" "Removing '$name'..."
|
|
262
|
+
Remove-BcContainer -containerName $name
|
|
263
|
+
Write-BCSuccess "'$name' removed."
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function Start-BCDContainer {
|
|
268
|
+
<#
|
|
269
|
+
.SYNOPSIS
|
|
270
|
+
Starts a stopped BC Docker container.
|
|
271
|
+
#>
|
|
272
|
+
[CmdletBinding()]
|
|
273
|
+
param([string]$ContainerName)
|
|
274
|
+
|
|
275
|
+
if (-not $ContainerName) {
|
|
276
|
+
$ContainerName = Select-BCContainer -Title "Select container to start"
|
|
277
|
+
if (-not $ContainerName) { return }
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
Write-BCStep "START" "Starting '$ContainerName'..."
|
|
281
|
+
docker start $ContainerName
|
|
282
|
+
Write-BCSuccess "Container '$ContainerName' started."
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function Stop-BCDContainer {
|
|
286
|
+
<#
|
|
287
|
+
.SYNOPSIS
|
|
288
|
+
Stops a running BC Docker container.
|
|
289
|
+
#>
|
|
290
|
+
[CmdletBinding()]
|
|
291
|
+
param([string]$ContainerName)
|
|
292
|
+
|
|
293
|
+
if (-not $ContainerName) {
|
|
294
|
+
$ContainerName = Select-BCContainer -Title "Select container to stop"
|
|
295
|
+
if (-not $ContainerName) { return }
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
Write-BCStep "STOP" "Stopping '$ContainerName'..."
|
|
299
|
+
docker stop $ContainerName
|
|
300
|
+
Write-BCSuccess "Container '$ContainerName' stopped."
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function Restart-BCDContainer {
|
|
304
|
+
<#
|
|
305
|
+
.SYNOPSIS
|
|
306
|
+
Restarts a BC Docker container.
|
|
307
|
+
#>
|
|
308
|
+
[CmdletBinding()]
|
|
309
|
+
param([string]$ContainerName)
|
|
310
|
+
|
|
311
|
+
if (-not $ContainerName) {
|
|
312
|
+
$ContainerName = Select-BCContainer -Title "Select container to restart"
|
|
313
|
+
if (-not $ContainerName) { return }
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
Write-BCStep "RESTART" "Restarting '$ContainerName'..."
|
|
317
|
+
docker restart $ContainerName
|
|
318
|
+
Write-BCSuccess "Container '$ContainerName' restarted."
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function Get-BCDContainers {
|
|
322
|
+
<#
|
|
323
|
+
.SYNOPSIS
|
|
324
|
+
Lists all BC Docker containers with their status.
|
|
325
|
+
#>
|
|
326
|
+
[CmdletBinding()]
|
|
327
|
+
param()
|
|
328
|
+
|
|
329
|
+
if (-not (Assert-BcContainerHelper)) { return }
|
|
330
|
+
|
|
331
|
+
Write-BCBanner "BC Containers"
|
|
332
|
+
|
|
333
|
+
$containers = Get-BcContainers
|
|
334
|
+
if ($containers.Count -eq 0) {
|
|
335
|
+
Write-BCInfo "No BC containers found."
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
foreach ($name in $containers) {
|
|
340
|
+
$inspect = docker inspect $name --format '{{.State.Status}}' 2>$null
|
|
341
|
+
$statusColor = if ($inspect -eq "running") { "Green" } else { "DarkGray" }
|
|
342
|
+
|
|
343
|
+
Write-Host " " -NoNewline
|
|
344
|
+
Write-Host $name.PadRight(25) -ForegroundColor White -NoNewline
|
|
345
|
+
Write-Host $inspect -ForegroundColor $statusColor
|
|
346
|
+
}
|
|
347
|
+
Write-Host ""
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function Get-BCDContainerInfo {
|
|
351
|
+
<#
|
|
352
|
+
.SYNOPSIS
|
|
353
|
+
Shows detailed info about a BC container: version, endpoints, credentials.
|
|
354
|
+
#>
|
|
355
|
+
[CmdletBinding()]
|
|
356
|
+
param([string]$ContainerName)
|
|
357
|
+
|
|
358
|
+
if (-not (Assert-BcContainerHelper)) { return }
|
|
359
|
+
|
|
360
|
+
if (-not $ContainerName) {
|
|
361
|
+
$ContainerName = Select-BCContainer -Title "Select container for info"
|
|
362
|
+
if (-not $ContainerName) { return }
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
$bcVersion = Get-BcContainerNavVersion -containerNameOrId $ContainerName
|
|
366
|
+
$webUrl = Get-BcContainerUrl -containerName $ContainerName -useHttps:$false
|
|
367
|
+
$inspect = docker inspect $ContainerName --format '{{.State.Status}}' 2>$null
|
|
368
|
+
$statusColor = if ($inspect -eq "running") { "Green" } else { "Red" }
|
|
369
|
+
|
|
370
|
+
Write-BCProperty "Container" $ContainerName
|
|
371
|
+
Write-BCProperty "Status" $inspect $statusColor
|
|
372
|
+
Write-BCProperty "BC Version" $bcVersion
|
|
373
|
+
Write-BCProperty "Web Client" $webUrl "Cyan"
|
|
374
|
+
Write-BCProperty "OData/API" "http://${ContainerName}:7048/BC/api" "Cyan"
|
|
375
|
+
Write-BCProperty "Dev Service" "http://${ContainerName}:7049/BC" "Cyan"
|
|
376
|
+
Write-Host ""
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function Open-BCDWebClient {
|
|
380
|
+
<#
|
|
381
|
+
.SYNOPSIS
|
|
382
|
+
Opens the BC Web Client in the default browser.
|
|
383
|
+
#>
|
|
384
|
+
[CmdletBinding()]
|
|
385
|
+
param([string]$ContainerName)
|
|
386
|
+
|
|
387
|
+
if (-not (Assert-BcContainerHelper)) { return }
|
|
388
|
+
|
|
389
|
+
if (-not $ContainerName) {
|
|
390
|
+
$ContainerName = Select-BCContainer -Title "Select container to open"
|
|
391
|
+
if (-not $ContainerName) { return }
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
$url = Get-BcContainerUrl -containerName $ContainerName -useHttps:$false
|
|
395
|
+
Write-BCInfo "Opening $url"
|
|
396
|
+
Start-Process $url
|
|
397
|
+
}
|