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.
@@ -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
+ }