Modules/businessdev.ALbuild.Containers/Private/ConvertTo-BcEncodedCommand.ps1
|
function ConvertTo-BcEncodedCommand { <# .SYNOPSIS Builds a Base64 -EncodedCommand payload for running a script block inside a container. .DESCRIPTION Internal, pure helper. Prepends a strict error preference, an import of the Business Central management module (the 'docker exec ... powershell -NoProfile' session does not load it, so NAV cmdlets such as Publish-NAVApp would otherwise be unrecognised) and optional variable assignments (passed as JSON to survive the process boundary), then UTF-16LE/Base64 encodes the result for 'powershell -EncodedCommand'. Pure (no I/O), so it is unit-testable. #> [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory)] [scriptblock] $ScriptBlock, [hashtable] $Variables = @{}, # Return the assembled script text instead of the Base64 payload. The caller can then decide # whether to pass it via -EncodedCommand or - for scripts too long for the command line - via # a staged -File. [switch] $AsText ) # Make the BC management cmdlets (Get-NAVAppInfo, Publish-/Sync-/Install-NAVApp, Get-NAVServerInstance, # ...) available inside the container; the 'docker exec' session does not auto-load them. Both the # assembly NAME/location and the PowerShell edition that can load it vary by BC version: # Windows PowerShell (.NET Framework) -> Microsoft.Dynamics.Nav.*.Management.dll (BC <= 28) # PowerShell 7 (.NET) -> Microsoft.BusinessCentral.*.Management.dll (BC 24+, .NET 8) # BC29 dropped the Windows PowerShell set entirely, so there the caller runs us under pwsh (see # Get-BcContainerPowerShellExe). Pick the assemblies matching the running edition (a .NET 8 DLL won't # load in Windows PowerShell and vice versa), search recursively (location moved across versions), and # import each. Tolerant (no throw) so commands needing no NAV cmdlets still run. # # Do NOT guard this with 'Get-Command <cmdlet-not-yet-loaded>'. Inside a BC container, pwsh dies on # that - exit 255, no stdout, no stderr, not catchable by try/catch - because command auto-discovery # scans C:\Windows\system32\WindowsPowerShell\v1.0\Modules, hits Desktop-edition-only modules and # triggers the Windows PowerShell Compatibility feature, which cannot start in the container. Testing # 'Get-Module' instead only inspects already-loaded modules, so it never triggers discovery. (This # mirrors Microsoft's own NavAdminTool.ps1, which also probes with Get-Module.) $importNavManagement = @' $__navDllNames = if ($PSVersionTable.PSEdition -eq 'Core') { @('Microsoft.BusinessCentral.Apps.Management.dll', 'Microsoft.BusinessCentral.Management.dll', 'Microsoft.Dynamics.Nav.Apps.Management.dll', 'Microsoft.Dynamics.Nav.Management.dll') } else { @('Microsoft.Dynamics.Nav.Apps.Management.dll', 'Microsoft.Dynamics.Nav.Management.dll') } foreach ($__navDll in $__navDllNames) { if (Get-Module -Name ([System.IO.Path]::GetFileNameWithoutExtension($__navDll))) { continue } foreach ($__navFile in @(Get-ChildItem 'C:\Program Files\Microsoft Dynamics NAV' -Recurse -Filter $__navDll -ErrorAction SilentlyContinue)) { try { Import-Module $__navFile.FullName -DisableNameChecking -ErrorAction Stop } catch { } } } '@ $sb = [System.Text.StringBuilder]::new() [void]$sb.AppendLine('$ErrorActionPreference = ''Stop''') [void]$sb.AppendLine($importNavManagement) foreach ($key in $Variables.Keys) { $json = ($Variables[$key] | ConvertTo-Json -Compress -Depth 20) $escaped = $json -replace "'", "''" [void]$sb.AppendLine("`$$key = '$escaped' | ConvertFrom-Json") } [void]$sb.AppendLine($ScriptBlock.ToString()) if ($AsText) { return $sb.ToString() } $bytes = [System.Text.Encoding]::Unicode.GetBytes($sb.ToString()) return [System.Convert]::ToBase64String($bytes) } |