Modules/businessdev.ALbuild.Containers/Public/Enter-BcContainer.ps1
|
function Enter-BcContainer { <# .SYNOPSIS Opens an interactive PowerShell session inside a Business Central container. .DESCRIPTION The equivalent of BcContainerHelper's Enter-BcContainer / Open-BcContainer: a prompt inside the container, for the times when you need to look rather than automate - reading a service tier log, checking what a failed install actually left behind, running Get-NAVAppInfo by hand. WHY THIS IS NOT Invoke-BcContainerCommand Invoke-BcContainerCommand captures stdout and stderr, which is exactly right for automation and exactly wrong for a session: a captured stream draws no prompt and accepts no keystrokes. This hands the console to 'docker exec -it' and gets out of the way, so the exit code you see is the shell's own. The in-container host is resolved by Get-BcContainerPowerShellExe, so this lands on the shell that can actually load the BC management cmdlets - 'powershell' up to BC28, 'pwsh' from BC29, where the Windows PowerShell compatibility layer was removed. Refuses to start without a terminal. 'docker exec -it' on a redirected stdin either fails with "the input device is not a TTY" or waits forever, and a pipeline job that hangs on an interactive prompt is far worse than one that says why it cannot run - so this checks first and points at Invoke-BcContainerCommand instead. .PARAMETER Name The container to enter. .PARAMETER Command A command to run before the prompt appears. The session stays open afterwards. .PARAMETER WorkingDirectory Where the session starts. Default 'C:\run', which is where the generic image keeps its scripts. .PARAMETER PowerShellExe Pin the in-container shell instead of letting Get-BcContainerPowerShellExe choose it. .PARAMETER SkipPrompt Do not dot-source the image's C:\Run\prompt.ps1. Use it when that script gets in the way. .PARAMETER DockerExecutable The Docker executable to use. Default 'docker'. .EXAMPLE Enter-BcContainer -Name bcserver A prompt inside 'bcserver', in C:\run, with the image's prompt loaded. .EXAMPLE Enter-BcContainer -Name bcserver -Command 'Get-NAVAppInfo -ServerInstance BC' Runs the command, then leaves the session open to keep looking. .EXAMPLE Open-BcContainer bcserver The BcContainerHelper name, kept as an alias. .OUTPUTS None. The exit code of the in-container shell is left in $LASTEXITCODE. #> [CmdletBinding()] [Alias('Open-BcContainer')] [OutputType([void])] param( [Parameter(Mandatory, Position = 0)] [ValidateNotNullOrEmpty()] [Alias('ContainerName')] [string] $Name, [string] $Command, [string] $WorkingDirectory = 'C:\run', [ValidateSet('powershell', 'pwsh')] [string] $PowerShellExe, [switch] $SkipPrompt, [string] $DockerExecutable = 'docker' ) # --- preflight: every failure here is cheaper than a hung prompt ----------------------------- # A session needs a real terminal on both ends. Checked before Docker is even asked, because this # is the failure a build agent would otherwise hit, and it would hit it by hanging. if ([System.Console]::IsInputRedirected -or [System.Console]::IsOutputRedirected) { throw "Enter-BcContainer needs an interactive terminal, and this session's input or output is redirected ('docker exec -it' would fail or hang). Use Invoke-BcContainerCommand to run something in '$Name' non-interactively." } $null = Test-BcDocker -DockerExecutable $DockerExecutable -Require $container = Get-BcContainer -Name $Name -DockerExecutable $DockerExecutable -ErrorAction SilentlyContinue if (-not $container) { throw "There is no container named '$Name'. 'Get-BcContainer' lists what is on this host." } if (-not $container.Running) { throw "Container '$Name' is $($container.Status), and a shell can only be opened in a running container. Start it with 'Start-BcContainer -Name $Name'." } $psExe = if ($PowerShellExe) { $PowerShellExe } else { Get-BcContainerPowerShellExe -Name $Name -DockerExecutable $DockerExecutable } $arguments = Get-BcContainerEnterArgument -ContainerName $Name -PowerShellExe $psExe ` -Command $Command -WorkingDirectory $WorkingDirectory -SkipPrompt:$SkipPrompt Write-ALbuildLog "Entering '$Name' with $psExe. Type 'exit' to return." # Called directly, NOT through Invoke-BcDocker: that captures the streams, and a captured stream # cannot be typed into. The native call inherits this console, which is the whole point. & $DockerExecutable @arguments $exit = $LASTEXITCODE # 130 is SIGINT (Ctrl+C) and is how a session is normally left - not a failure worth a warning. if ($exit -ne 0 -and $exit -ne 130) { Write-ALbuildLog -Level Warning "The session in '$Name' ended with exit code $exit." } } |