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/dist/server.js ADDED
@@ -0,0 +1,203 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { z } from "zod";
4
+ import { runPowerShell, runRawPowerShell } from "./executor.js";
5
+ const server = new McpServer({
6
+ name: "bcd",
7
+ version: "1.0.0",
8
+ });
9
+ // ── Container Tools ──────────────────────────────────────
10
+ server.tool("list-containers", "List all Business Central Docker containers with their running status", {}, async () => {
11
+ const result = await runRawPowerShell(`
12
+ Import-Module BcContainerHelper -DisableNameChecking -ErrorAction Stop
13
+ $containers = Get-BcContainers
14
+ if ($containers.Count -eq 0) {
15
+ Write-Output "No BC containers found."
16
+ } else {
17
+ foreach ($name in $containers) {
18
+ $status = docker inspect $name --format '{{.State.Status}}' 2>$null
19
+ Write-Output "$name [$status]"
20
+ }
21
+ }
22
+ `);
23
+ return { content: [{ type: "text", text: result.stdout || "No containers found." }] };
24
+ });
25
+ server.tool("container-info", "Get detailed info about a BC container: version, status, and service endpoints", { containerName: z.string().describe("Name of the BC container") }, async ({ containerName }) => {
26
+ const result = await runRawPowerShell(`
27
+ Import-Module BcContainerHelper -DisableNameChecking -ErrorAction Stop
28
+ $bcVersion = Get-BcContainerNavVersion -containerNameOrId '${containerName}'
29
+ $webUrl = Get-BcContainerUrl -containerName '${containerName}' -useHttps:$false
30
+ $status = docker inspect '${containerName}' --format '{{.State.Status}}' 2>$null
31
+ Write-Output "Container : ${containerName}"
32
+ Write-Output "Status : $status"
33
+ Write-Output "BC Version : $bcVersion"
34
+ Write-Output "Web Client : $webUrl"
35
+ Write-Output "OData/API : http://${containerName}:7048/BC/api"
36
+ Write-Output "Dev Service: http://${containerName}:7049/BC"
37
+ `);
38
+ return { content: [{ type: "text", text: result.stdout }] };
39
+ });
40
+ server.tool("create-container", "Create a new Business Central Docker container with test toolkit. Long-running (5-30 min).", {
41
+ containerName: z.string().default("bcsandbox").describe("Docker container name"),
42
+ version: z.string().default("sandbox").describe("BC version: 'sandbox', 'onprem', or specific like '26.0'"),
43
+ country: z.string().default("us").describe("Localization: us, w1, gb, nl, dk, de, etc."),
44
+ userName: z.string().default("admin").describe("Admin username"),
45
+ password: z.string().default("P@ssw0rd!").describe("Admin password"),
46
+ memoryLimit: z.string().default("8G").describe("Memory limit: 8G, 12G, 16G"),
47
+ isolation: z.enum(["hyperv", "process"]).default("hyperv").describe("Isolation mode"),
48
+ testToolkit: z.enum(["none", "libraries", "full"]).default("libraries").describe("Test toolkit: none, libraries (faster), or full (all MS tests)"),
49
+ bypassCDN: z.boolean().default(false).describe("Skip Azure CDN, download from blob storage directly"),
50
+ licenseFile: z.string().optional().describe("Path or URL to .bclicense / .flf license file"),
51
+ }, async ({ containerName, version, country, userName, password, memoryLimit, isolation, testToolkit, bypassCDN, licenseFile }) => {
52
+ const includeToolkit = testToolkit !== "none" ? "$true" : "$false";
53
+ const libOnly = testToolkit === "libraries" ? "$true" : "$false";
54
+ const cdn = bypassCDN ? "-BypassCDN" : "";
55
+ const lic = licenseFile ? `-LicenseFile '${licenseFile}'` : "";
56
+ const result = await runPowerShell(`
57
+ New-BCDContainer \`
58
+ -ContainerName '${containerName}' \`
59
+ -Version '${version}' \`
60
+ -Country '${country}' \`
61
+ -UserName '${userName}' \`
62
+ -Password '${password}' \`
63
+ -MemoryLimit '${memoryLimit}' \`
64
+ -Isolation '${isolation}' \`
65
+ -IncludeTestToolkit ${includeToolkit} \`
66
+ -TestLibrariesOnly ${libOnly} \`
67
+ ${cdn} ${lic}
68
+ `, 1_800_000);
69
+ return { content: [{ type: "text", text: result.stdout || "Container creation complete." }] };
70
+ });
71
+ server.tool("remove-container", "Remove a Business Central Docker container", { containerName: z.string().describe("Name of the container to remove") }, async ({ containerName }) => {
72
+ const result = await runPowerShell(`Remove-BCDContainer -ContainerName '${containerName}'`);
73
+ return { content: [{ type: "text", text: result.stdout || `Container '${containerName}' removed.` }] };
74
+ });
75
+ server.tool("start-container", "Start a stopped BC Docker container", { containerName: z.string().describe("Name of the container to start") }, async ({ containerName }) => {
76
+ const result = await runPowerShell(`Start-BCDContainer -ContainerName '${containerName}'`);
77
+ return { content: [{ type: "text", text: result.stdout || `Container '${containerName}' started.` }] };
78
+ });
79
+ server.tool("stop-container", "Stop a running BC Docker container", { containerName: z.string().describe("Name of the container to stop") }, async ({ containerName }) => {
80
+ const result = await runPowerShell(`Stop-BCDContainer -ContainerName '${containerName}'`);
81
+ return { content: [{ type: "text", text: result.stdout || `Container '${containerName}' stopped.` }] };
82
+ });
83
+ server.tool("restart-container", "Restart a BC Docker container", { containerName: z.string().describe("Name of the container to restart") }, async ({ containerName }) => {
84
+ const result = await runPowerShell(`Restart-BCDContainer -ContainerName '${containerName}'`);
85
+ return { content: [{ type: "text", text: result.stdout || `Container '${containerName}' restarted.` }] };
86
+ });
87
+ server.tool("open-webclient", "Open the BC Web Client URL for a container. Returns the URL.", { containerName: z.string().describe("Name of the BC container") }, async ({ containerName }) => {
88
+ const result = await runRawPowerShell(`
89
+ Import-Module BcContainerHelper -DisableNameChecking -ErrorAction Stop
90
+ $url = Get-BcContainerUrl -containerName '${containerName}' -useHttps:$false
91
+ Write-Output $url
92
+ `);
93
+ return { content: [{ type: "text", text: result.stdout || "Could not resolve URL." }] };
94
+ });
95
+ // ── App Tools ────────────────────────────────────────────
96
+ server.tool("list-apps", "List all apps installed in a BC container, optionally filtered by publisher", {
97
+ containerName: z.string().describe("Target container name"),
98
+ publisher: z.string().optional().describe("Filter by publisher (e.g. 'Microsoft')"),
99
+ }, async ({ containerName, publisher }) => {
100
+ const filter = publisher
101
+ ? `| Where-Object { $_.Publisher -eq '${publisher}' }`
102
+ : "";
103
+ const result = await runRawPowerShell(`
104
+ Import-Module BcContainerHelper -DisableNameChecking -ErrorAction Stop
105
+ $apps = Get-BcContainerAppInfo -containerName '${containerName}' -tenant default -tenantSpecificProperties ${filter}
106
+ $apps | Select-Object Name, Publisher, Version, IsInstalled, Scope |
107
+ Sort-Object Publisher, Name |
108
+ Format-Table -AutoSize | Out-String -Width 200
109
+ `);
110
+ return { content: [{ type: "text", text: result.stdout || "No apps found." }] };
111
+ });
112
+ server.tool("install-app", "Publish and install a .app file into a BC container", {
113
+ containerName: z.string().describe("Target container name"),
114
+ appFile: z.string().describe("Full path to the .app file"),
115
+ userName: z.string().default("admin").describe("Admin username"),
116
+ password: z.string().default("P@ssw0rd!").describe("Admin password"),
117
+ }, async ({ containerName, appFile, userName, password }) => {
118
+ const result = await runPowerShell(`
119
+ $cred = Get-BCCredential -UserName '${userName}' -Password '${password}'
120
+ Install-BCDApp -ContainerName '${containerName}' -AppFile '${appFile}' -Credential $cred
121
+ `);
122
+ return { content: [{ type: "text", text: result.stdout || "App installed." }] };
123
+ });
124
+ server.tool("uninstall-app", "Uninstall an app from a BC container by name. Automatically handles dependency order.", {
125
+ containerName: z.string().describe("Target container name"),
126
+ appName: z.string().describe("Name of the app to uninstall"),
127
+ appPublisher: z.string().describe("Publisher of the app"),
128
+ }, async ({ containerName, appName, appPublisher }) => {
129
+ const result = await runRawPowerShell(`
130
+ Import-Module BcContainerHelper -DisableNameChecking -ErrorAction Stop
131
+ $allApps = Get-BcContainerAppInfo -containerName '${containerName}' -tenant default -tenantSpecificProperties
132
+ $sorted = Get-BcContainerAppInfo -containerName '${containerName}' -tenant default -tenantSpecificProperties -sort DependenciesLast
133
+ $target = $allApps | Where-Object { $_.Name -eq '${appName}' -and $_.Publisher -eq '${appPublisher}' }
134
+ if (-not $target) { Write-Output "App '${appName}' by '${appPublisher}' not found."; exit }
135
+ foreach ($app in $sorted) {
136
+ if ($app.Name -eq '${appName}' -and $app.Publisher -eq '${appPublisher}' -and $app.IsInstalled) {
137
+ UnInstall-BcContainerApp -name $app.Name -containerName '${containerName}' -publisher $app.Publisher -version $app.Version -force
138
+ Write-Output "Uninstalled: $($app.Name) v$($app.Version)"
139
+ }
140
+ }
141
+ `);
142
+ return { content: [{ type: "text", text: result.stdout || "Uninstall complete." }] };
143
+ });
144
+ server.tool("publish-project", "Compile and publish an AL project folder into a BC container", {
145
+ containerName: z.string().describe("Target container name"),
146
+ projectFolder: z.string().describe("Full path to the AL project folder"),
147
+ userName: z.string().default("admin").describe("Admin username"),
148
+ password: z.string().default("P@ssw0rd!").describe("Admin password"),
149
+ }, async ({ containerName, projectFolder, userName, password }) => {
150
+ const result = await runPowerShell(`
151
+ $cred = Get-BCCredential -UserName '${userName}' -Password '${password}'
152
+ Publish-BCDProject -ContainerName '${containerName}' -ProjectFolder '${projectFolder}' -Credential $cred
153
+ `, 300_000);
154
+ return { content: [{ type: "text", text: result.stdout || "Project compiled and published." }] };
155
+ });
156
+ // ── Test & License Tools ─────────────────────────────────
157
+ server.tool("import-test-toolkit", "Import the BC Test Toolkit (libraries only or full framework) into a container", {
158
+ containerName: z.string().describe("Target container name"),
159
+ librariesOnly: z.boolean().default(true).describe("true = just helper libs (faster), false = full MS test codeunits"),
160
+ userName: z.string().default("admin").describe("Admin username"),
161
+ password: z.string().default("P@ssw0rd!").describe("Admin password"),
162
+ }, async ({ containerName, librariesOnly, userName, password }) => {
163
+ const result = await runPowerShell(`
164
+ $cred = Get-BCCredential -UserName '${userName}' -Password '${password}'
165
+ Import-BCDTestToolkit -ContainerName '${containerName}' -Credential $cred -LibrariesOnly $${librariesOnly}
166
+ `, 300_000);
167
+ return { content: [{ type: "text", text: result.stdout || "Test toolkit imported." }] };
168
+ });
169
+ server.tool("import-license", "Import a license file into a BC container", {
170
+ containerName: z.string().describe("Target container name"),
171
+ licenseFile: z.string().describe("Full path or URL to .bclicense / .flf file"),
172
+ }, async ({ containerName, licenseFile }) => {
173
+ const result = await runPowerShell(`Import-BCDLicense -ContainerName '${containerName}' -LicenseFile '${licenseFile}'`);
174
+ return { content: [{ type: "text", text: result.stdout || "License imported." }] };
175
+ });
176
+ server.tool("run-tests", "Run AL tests in a BC container. Can run all tests, a specific codeunit, or compile-and-run from a project folder.", {
177
+ containerName: z.string().default("bcsandbox").describe("Target container name"),
178
+ testCodeunitId: z.number().optional().describe("Specific test codeunit ID (omit for all)"),
179
+ testFunctionName: z.string().optional().describe("Specific test function name"),
180
+ appProjectFolder: z.string().optional().describe("AL test project folder to compile/publish/run"),
181
+ userName: z.string().default("admin").describe("Admin username"),
182
+ password: z.string().default("P@ssw0rd!").describe("Admin password"),
183
+ }, async ({ containerName, testCodeunitId, testFunctionName, appProjectFolder, userName, password }) => {
184
+ const params = [`-ContainerName '${containerName}'`];
185
+ params.push(`-Credential (Get-BCCredential -UserName '${userName}' -Password '${password}')`);
186
+ if (testCodeunitId)
187
+ params.push(`-TestCodeunitId ${testCodeunitId}`);
188
+ if (testFunctionName)
189
+ params.push(`-TestFunctionName '${testFunctionName}'`);
190
+ if (appProjectFolder)
191
+ params.push(`-AppProjectFolder '${appProjectFolder}'`);
192
+ const result = await runPowerShell(`Invoke-BCDTests ${params.join(" ")}`, 600_000);
193
+ return { content: [{ type: "text", text: result.stdout || "Test run complete." }] };
194
+ });
195
+ // ── Start ────────────────────────────────────────────────
196
+ async function main() {
197
+ const transport = new StdioServerTransport();
198
+ await server.connect(transport);
199
+ }
200
+ main().catch((err) => {
201
+ console.error("MCP server failed:", err);
202
+ process.exit(1);
203
+ });
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "bcdocker",
3
+ "version": "1.0.0",
4
+ "description": "MCP server and CLI for Business Central Docker container management. Create, manage, and test BC containers from your terminal or AI assistant.",
5
+ "type": "module",
6
+ "bin": {
7
+ "bcd": "./dist/cli.js"
8
+ },
9
+ "main": "./dist/server.js",
10
+ "files": [
11
+ "dist",
12
+ "ps",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "start": "node dist/server.js",
19
+ "cli": "node dist/cli.js",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "keywords": [
23
+ "business-central",
24
+ "dynamics-365",
25
+ "docker",
26
+ "bc",
27
+ "al",
28
+ "mcp",
29
+ "model-context-protocol",
30
+ "bccontainerhelper",
31
+ "devops",
32
+ "cli"
33
+ ],
34
+ "author": "Oleksandr (Ciellos)",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/olederkach/bcdocker.git"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/olederkach/bcdocker/issues"
42
+ },
43
+ "homepage": "https://github.com/olederkach/bcdocker#readme",
44
+ "engines": {
45
+ "node": ">=18.0.0"
46
+ },
47
+ "os": [
48
+ "win32"
49
+ ],
50
+ "dependencies": {
51
+ "@modelcontextprotocol/sdk": "^1.12.0",
52
+ "commander": "^13.0.0",
53
+ "zod": "^3.25.0"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^22.0.0",
57
+ "typescript": "^5.7.0"
58
+ }
59
+ }
package/ps/Apps.ps1 ADDED
@@ -0,0 +1,360 @@
1
+ function Get-BCDApps {
2
+ <#
3
+ .SYNOPSIS
4
+ Lists all apps installed in a BC container. Optionally filters by publisher.
5
+
6
+ .PARAMETER ContainerName
7
+ Target container. Prompts with picker if omitted.
8
+
9
+ .PARAMETER Publisher
10
+ Filter by publisher name (e.g. "Microsoft", "Contoso").
11
+
12
+ .PARAMETER GridView
13
+ Show results in Out-GridView instead of console.
14
+ #>
15
+ [CmdletBinding()]
16
+ param(
17
+ [string]$ContainerName,
18
+ [string]$Publisher,
19
+ [switch]$GridView
20
+ )
21
+
22
+ if (-not (Assert-BcContainerHelper)) { return }
23
+
24
+ if (-not $ContainerName) {
25
+ $ContainerName = Select-BCContainer -Title "Select container to list apps"
26
+ if (-not $ContainerName) { return }
27
+ }
28
+
29
+ $apps = Get-BcContainerAppInfo -containerName $ContainerName -tenant default -tenantSpecificProperties
30
+
31
+ if ($Publisher) {
32
+ $apps = $apps | Where-Object { $_.Publisher -eq $Publisher }
33
+ }
34
+
35
+ $result = foreach ($app in $apps) {
36
+ [PSCustomObject]@{
37
+ Name = $app.Name
38
+ Publisher = $app.Publisher
39
+ Version = $app.Version
40
+ Scope = $app.Scope
41
+ IsInstalled = $app.IsInstalled
42
+ SyncState = $app.SyncState
43
+ }
44
+ }
45
+
46
+ if ($GridView) {
47
+ $result | Sort-Object Publisher, Name | Out-GridView -Title "Apps in $ContainerName"
48
+ }
49
+ else {
50
+ $result | Sort-Object Publisher, Name | Format-Table -AutoSize
51
+ }
52
+ }
53
+
54
+ function Install-BCDApp {
55
+ <#
56
+ .SYNOPSIS
57
+ Publishes and installs .app file(s) into a BC container.
58
+
59
+ .PARAMETER ContainerName
60
+ Target container.
61
+
62
+ .PARAMETER AppFile
63
+ Path(s) to .app file(s). Opens file picker if omitted.
64
+
65
+ .PARAMETER Credential
66
+ Container credentials.
67
+
68
+ .PARAMETER UseDevEndpoint
69
+ Publish via dev endpoint. Default: $true
70
+ #>
71
+ [CmdletBinding()]
72
+ param(
73
+ [string]$ContainerName,
74
+ [string[]]$AppFile,
75
+ [PSCredential]$Credential,
76
+ [bool]$UseDevEndpoint = $true
77
+ )
78
+
79
+ if (-not (Assert-BcContainerHelper)) { return }
80
+
81
+ if (-not $ContainerName) {
82
+ $ContainerName = Select-BCContainer -Title "Select container to install app"
83
+ if (-not $ContainerName) { return }
84
+ }
85
+
86
+ if (-not $AppFile) {
87
+ $AppFile = Select-AppFiles -Title "Select .app file(s) to install"
88
+ if (-not $AppFile) { return }
89
+ }
90
+
91
+ if (-not $Credential) {
92
+ $Credential = Get-BCCredential
93
+ }
94
+
95
+ Write-BCBanner "Install App(s)"
96
+
97
+ foreach ($file in $AppFile) {
98
+ $fileName = Split-Path $file -Leaf
99
+ Write-BCStep "PUB" "Publishing $fileName..."
100
+
101
+ $params = @{
102
+ containerName = $ContainerName
103
+ appFile = $file
104
+ credential = $Credential
105
+ install = $true
106
+ sync = $true
107
+ syncMode = "ForceSync"
108
+ skipVerification = $true
109
+ }
110
+
111
+ if ($UseDevEndpoint) {
112
+ $params.useDevEndpoint = $true
113
+ }
114
+
115
+ Publish-BcContainerApp @params
116
+ Write-BCSuccess "$fileName installed."
117
+ }
118
+ }
119
+
120
+ function Uninstall-BCDApp {
121
+ <#
122
+ .SYNOPSIS
123
+ Uninstalls selected apps from a BC container, resolving dependency order.
124
+
125
+ .PARAMETER ContainerName
126
+ Target container.
127
+
128
+ .PARAMETER IncludeMicrosoft
129
+ Show Microsoft apps in the selection list. Default: $false
130
+ #>
131
+ [CmdletBinding()]
132
+ param(
133
+ [string]$ContainerName,
134
+ [switch]$IncludeMicrosoft
135
+ )
136
+
137
+ if (-not (Assert-BcContainerHelper)) { return }
138
+
139
+ if (-not $ContainerName) {
140
+ $ContainerName = Select-BCContainer -Title "Select container to uninstall apps from"
141
+ if (-not $ContainerName) { return }
142
+ }
143
+
144
+ $allApps = Get-BcContainerAppInfo -containerName $ContainerName -tenant default -tenantSpecificProperties
145
+
146
+ $installable = $allApps | Where-Object { $_.IsInstalled }
147
+ if (-not $IncludeMicrosoft) {
148
+ $installable = $installable | Where-Object { $_.Publisher -ne "Microsoft" }
149
+ }
150
+
151
+ if ($installable.Count -eq 0) {
152
+ Write-BCInfo "No uninstallable apps found."
153
+ return
154
+ }
155
+
156
+ $selected = $installable |
157
+ Select-Object Name, Publisher, Version |
158
+ Sort-Object Name |
159
+ Out-GridView -PassThru -Title "Select app(s) to uninstall"
160
+
161
+ if (-not $selected) { return }
162
+
163
+ Write-BCBanner "Uninstall Apps"
164
+
165
+ $sorted = Get-BcContainerAppInfo -containerName $ContainerName -tenant default -tenantSpecificProperties -sort DependenciesLast
166
+
167
+ $toRemove = @{}
168
+ foreach ($app in $selected) {
169
+ $key = "$($app.Name)|$($app.Publisher)"
170
+ $toRemove[$key] = $true
171
+ Add-BCDDependentApps -AppName $app.Name -AppPublisher $app.Publisher -Map $toRemove -AllApps $allApps
172
+ }
173
+
174
+ $ordered = $sorted | Where-Object {
175
+ $toRemove.ContainsKey("$($_.Name)|$($_.Publisher)")
176
+ }
177
+
178
+ foreach ($app in $ordered) {
179
+ Write-BCStep "DEL" "Uninstalling $($app.Name) v$($app.Version)..."
180
+ try {
181
+ UnInstall-BcContainerApp -name $app.Name -containerName $ContainerName `
182
+ -publisher $app.Publisher -version $app.Version -force -ErrorAction Stop
183
+ Write-BCSuccess "$($app.Name) uninstalled."
184
+ }
185
+ catch {
186
+ Write-BCError "Failed: $_"
187
+ }
188
+ }
189
+ }
190
+
191
+ function Add-BCDDependentApps {
192
+ param(
193
+ [string]$AppName,
194
+ [string]$AppPublisher,
195
+ [hashtable]$Map,
196
+ $AllApps
197
+ )
198
+
199
+ $AllApps | Where-Object { $_.IsInstalled -and $_.Dependencies } | ForEach-Object {
200
+ $dependent = $_
201
+ $_.Dependencies | ForEach-Object {
202
+ $parts = $_ -split ','
203
+ if ($parts.Count -ge 2) {
204
+ $depName = $parts[0].Trim()
205
+ $depPub = $parts[1].Trim()
206
+ if ($depName -eq $AppName -and $depPub -eq $AppPublisher) {
207
+ $key = "$($dependent.Name)|$($dependent.Publisher)"
208
+ if (-not $Map.ContainsKey($key)) {
209
+ $Map[$key] = $true
210
+ Add-BCDDependentApps -AppName $dependent.Name -AppPublisher $dependent.Publisher -Map $Map -AllApps $AllApps
211
+ }
212
+ }
213
+ }
214
+ }
215
+ }
216
+ }
217
+
218
+ function Publish-BCDProject {
219
+ <#
220
+ .SYNOPSIS
221
+ Compiles and publishes an AL project into a BC container.
222
+
223
+ .PARAMETER ContainerName
224
+ Target container.
225
+
226
+ .PARAMETER ProjectFolder
227
+ Path to the AL project folder. Opens folder picker if omitted.
228
+
229
+ .PARAMETER Credential
230
+ Container credentials.
231
+ #>
232
+ [CmdletBinding()]
233
+ param(
234
+ [string]$ContainerName,
235
+ [string]$ProjectFolder,
236
+ [PSCredential]$Credential
237
+ )
238
+
239
+ if (-not (Assert-BcContainerHelper)) { return }
240
+
241
+ if (-not $ContainerName) {
242
+ $ContainerName = Select-BCContainer -Title "Select container to publish to"
243
+ if (-not $ContainerName) { return }
244
+ }
245
+
246
+ if (-not $ProjectFolder) {
247
+ $ProjectFolder = Select-Folder -Description "Select AL project folder"
248
+ if (-not $ProjectFolder) { return }
249
+ }
250
+
251
+ if (-not $Credential) {
252
+ $Credential = Get-BCCredential
253
+ }
254
+
255
+ Write-BCBanner "Compile & Publish"
256
+ Write-BCStep "1/2" "Compiling $ProjectFolder..."
257
+
258
+ $appFile = Compile-AppInBcContainer `
259
+ -containerName $ContainerName `
260
+ -appProjectFolder $ProjectFolder `
261
+ -credential $Credential `
262
+ -UpdateSymbols
263
+
264
+ Write-BCSuccess "Compiled: $appFile"
265
+
266
+ Write-BCStep "2/2" "Publishing..."
267
+ Publish-BcContainerApp `
268
+ -containerName $ContainerName `
269
+ -appFile $appFile `
270
+ -credential $Credential `
271
+ -install -sync `
272
+ -syncMode ForceSync `
273
+ -skipVerification `
274
+ -useDevEndpoint
275
+
276
+ Write-BCSuccess "Published and installed."
277
+ }
278
+
279
+ function Import-BCDTestToolkit {
280
+ <#
281
+ .SYNOPSIS
282
+ Imports the BC Test Toolkit into a container.
283
+
284
+ .PARAMETER ContainerName
285
+ Target container.
286
+
287
+ .PARAMETER Credential
288
+ Container credentials.
289
+
290
+ .PARAMETER LibrariesOnly
291
+ Install only test libraries (no Microsoft test codeunits). Default: $true
292
+ #>
293
+ [CmdletBinding()]
294
+ param(
295
+ [string]$ContainerName,
296
+ [PSCredential]$Credential,
297
+ [bool]$LibrariesOnly = $true
298
+ )
299
+
300
+ if (-not (Assert-BcContainerHelper)) { return }
301
+
302
+ if (-not $ContainerName) {
303
+ $ContainerName = Select-BCContainer -Title "Select container for test toolkit"
304
+ if (-not $ContainerName) { return }
305
+ }
306
+
307
+ if (-not $Credential) {
308
+ $Credential = Get-BCCredential
309
+ }
310
+
311
+ $mode = if ($LibrariesOnly) { "libraries only" } else { "full framework" }
312
+ Write-BCBanner "Import Test Toolkit ($mode)"
313
+
314
+ Import-TestToolkitToBcContainer `
315
+ -containerName $ContainerName `
316
+ -credential $Credential `
317
+ -includeTestLibrariesOnly:$LibrariesOnly
318
+
319
+ Write-BCSuccess "Test Toolkit imported."
320
+ }
321
+
322
+ function Import-BCDLicense {
323
+ <#
324
+ .SYNOPSIS
325
+ Imports a license file into a BC container.
326
+
327
+ .PARAMETER ContainerName
328
+ Target container.
329
+
330
+ .PARAMETER LicenseFile
331
+ Path or URL to the license file. Opens file picker if omitted.
332
+ #>
333
+ [CmdletBinding()]
334
+ param(
335
+ [string]$ContainerName,
336
+ [string]$LicenseFile
337
+ )
338
+
339
+ if (-not (Assert-BcContainerHelper)) { return }
340
+
341
+ if (-not $ContainerName) {
342
+ $ContainerName = Select-BCContainer -Title "Select container for license"
343
+ if (-not $ContainerName) { return }
344
+ }
345
+
346
+ if (-not $LicenseFile) {
347
+ Add-Type -AssemblyName System.Windows.Forms
348
+ $dialog = New-Object System.Windows.Forms.OpenFileDialog
349
+ $dialog.Filter = "BC License files (*.bclicense;*.flf)|*.bclicense;*.flf|All files (*.*)|*.*"
350
+ $dialog.Title = "Select license file"
351
+ if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
352
+ $LicenseFile = $dialog.FileName
353
+ }
354
+ else { return }
355
+ }
356
+
357
+ Write-BCBanner "Import License"
358
+ Import-BcContainerLicense -licenseFile $LicenseFile -containerName $ContainerName -restart
359
+ Write-BCSuccess "License imported and service restarted."
360
+ }
@@ -0,0 +1,30 @@
1
+ $ModuleRoot = $PSScriptRoot
2
+
3
+ . (Join-Path $ModuleRoot "Helpers.ps1")
4
+ . (Join-Path $ModuleRoot "Container.ps1")
5
+ . (Join-Path $ModuleRoot "Apps.ps1")
6
+ . (Join-Path $ModuleRoot "Tests.ps1")
7
+
8
+ Export-ModuleMember -Function @(
9
+ 'New-BCDContainer'
10
+ 'Remove-BCDContainer'
11
+ 'Start-BCDContainer'
12
+ 'Stop-BCDContainer'
13
+ 'Restart-BCDContainer'
14
+ 'Get-BCDContainers'
15
+ 'Get-BCDContainerInfo'
16
+ 'Open-BCDWebClient'
17
+ 'Get-BCDApps'
18
+ 'Install-BCDApp'
19
+ 'Uninstall-BCDApp'
20
+ 'Publish-BCDProject'
21
+ 'Import-BCDTestToolkit'
22
+ 'Import-BCDLicense'
23
+ 'Invoke-BCDTests'
24
+ 'Show-BCDMainMenu'
25
+ 'Show-BCDContainerForm'
26
+ 'Get-BCCredential'
27
+ 'Select-BCContainer'
28
+ 'Select-AppFiles'
29
+ 'Select-Folder'
30
+ )