HomeDockerInvoke-ScriptInBcContainer | Freddys blog

Invoke-ScriptInBcContainer | Freddys blog

As mentioned in this blog post, the Invoke-ScriptInBcContainer has undergone some critical changes in BcContainerHelper 3.0.9, which just shipped.

This blog post will portray some details about how this function works.

The function takes a containerName, a scriptblock and an argument list as parameters and will execute this scriptblock inside the container.

The function does however have 2 ways of performing the exact same function. If the user is using PowerShell as administrator AND the setting called usePsSession is not specifically set to untrue, then the function will use PowerShell remoting to execute the PowerShell code snippet inside the container.

PowerShell remoting is only available when operating with elevated permissions (as administrator). If you are not, or if you have set usePsSession to untrue – then the function will use docker exec to execute the PowerShell code snippet inside the container.

Using PowerShell remoting

Invoking a PowerShell snippet using PowerShell remoting is done by establishing a PowerShell Session to the container (caching that session for prospective use) and invoking the scriptblock using that session.

Establishing a PowerShell session takes approx. 0.9 second (on my machine). Any subsequent usages of that session take approx. 4 milliseconds. The caching of the session is very necessary for performance on chatty scripts. To illustrate this, you can run this script:

$bcContainerHelperConfig.usePsSession = $true
$containerName = "bcserver"
Remove-BcContainerSession -containerName $containerName

Measure-Command {
    $what = Invoke-scriptInBcContainer $containerName -scriptBlock { Param($year)
        "Summer of $year"
    } -argumentList 69
} | ForEach-Object { Write-Host "Time spent: $($_.TotalMilliseconds)" }
Write-Host -ForegroundColor Yellow $what

$cnt = 100
Measure-Command {
    1..$cnt | % {
        $what = Invoke-scriptInBcContainer $containerName -scriptBlock { Param($year)
            "Summer of $year"
        } -argumentList 69
} | ForEach-Object { Write-Host "Time spent: $($_.TotalMilliseconds/$cnt)" }
Write-Host -ForegroundColor Yellow $what

The output of this script on my machine is:

Invoke ScriptInBcContainer Freddys blog

NOTE that the subsequent runs ONLY is if the Invoke-ScriptInBcContainer happens in the SAME PowerShell session on the host.

Using Docker Exec

If PowerShell remoting is not possible (or you elect not to use it), BcContainerHelper will wrap your scriptblock in some pre- and post- code to best simulate that you are operating in a session inside the container and write this PowerShell script to the disk in a folder, which is shared with the container (under the hostHelperFolder on the host).

Now it will serialize the parameters, converting all secureStrings to encrypted strings (else they would not be accessible inside the container) and use docker exec to run the PowerShell script file.

The return value is written to another file, picked up by the host after docker exec.

Sounds cumbersome, but wrapped inside Invoke-ScriptInBcContainer is becomes easy to use. Running the same script as above (just with usePsSession set to untrue), reveals:

1653961248 306 Invoke ScriptInBcContainer Freddys blog

In this case, it doesn’t really matter whether the subsequent calls are in seperate PowerShell sessions.

Two ways of operating your pipelines/workflows

When operating on Azure DevOps and using 1 DevOps task to create the container, another 1 to compile and another 1 to publish – each of these tasks are seperate PowerShell sessions and the difference between operating using PowerShell remoting or Docker exec might not be as outspoken.

When operating the Run-AlPipeline function (the do-it-all function used in later versions of the CI/CD HOL and also AL-Go for GitHub), everything happens in 1 PowerShell session, and it can really take advantage of using PowerShell sessions. Here we might see a 25% performance cut by using Docker exec.

AL-Go for GitHub even provides a RemoveBcContainer override when calling Run-AlPipeline in order to remove the session by killing the underlying process instead of removing the session and probably hanging.

If you are using individual tasks, you have 2 options to avoid the wellknown freezing DevOps tasks:

  1. Set UsePsSession to untrue
  2. Wrap your code in a try / finally, where you use Remove-BcContainerSession with -killPsSessionProcess in the finally section of your task.

Error handling in Invoke-ScriptInBcContainer

In the latest version of BcContainerHelper, error handling using Invoke-ScriptInBcContainer has improved significantly.

Try operating this script with UsePsSession true or untrue:

$appInfo = Invoke-scriptInBcContainer $containerName -scriptBlock {
    Get-NavAppInfo -ServerInstance X

As there is no serverInstance X – it will fail, but it does add some additional information:

1653961248 991 Invoke ScriptInBcContainer Freddys blog

this was certainly not the case before 3.0.9:-)

As a bonus, it will actually dump all event log entries that have been added while the script was operating. It will also investigate whether any of the event log entries is an out-of-memory exception and if that is the case, it will re-throw an out-of-memory exception.

You will also see whether your script caused the ServiceTier to crash and end up as not operating.

Returning objects

Now try to run this script:

$appInfo = Invoke-scriptInBcContainer -containerName $containerName -scriptBlock {
    Get-NavAppInfo -ServerInstance $serverInstance
Write-Host -ForegroundColor Yellow "There are $($appInfo.Count) apps"
$appInfo[0] | Out-Host

You will see a slightly different output. When operating using a PsSession, the serialized objects have a few extra fields added:

1653961248 204 Invoke ScriptInBcContainer Freddys blog

The PSComputerName and RunspaceId are added to all objects serialized from Invoke-ScriptInBcontainer to the host. You can just ignore these; I do not use them for anything and I haven’t done any effort to try and remove them.

You cannot return a SecureString

Only known limitation to using Invoke-ScriptInBcContainer with Docker Exec is, that you cannot return a SecureString from the container. Oher than that, there should be parity between the functionality of using PsSession and Docker Exec.

If not, please file an issue on https://github.com/microsoft/navcontainerhelper/issues.


Freddy Kristiansen
Technical Evangelist


Most Popular