ContainerHandling/Invoke-ScriptInNavContainer.ps1

<#
 .Synopsis
  Invoke a PowerShell scriptblock in a NAV/BC Container
 .Description
  If you are running as administrator, this function will create a session to a Container and invoke a scriptblock in this session.
  If you are not an administrator, this function will create a PowerShell script in the container and use docker exec to launch the PowerShell script in the container.
 .Parameter containerName
  Name of the container in which you want to invoke a PowerShell scriptblock
 .Parameter scriptblock
  A pre-compiled PowerShell scriptblock to invoke
 .Parameter argumentList
  Arguments to transfer to the scriptblock in form of an object[]
 .Example
  Invoke-ScriptInBcContainer -containerName dev -scriptblock { $env:UserName }
 .Example
  [xml](Invoke-ScriptInBcContainer -containerName dev -scriptblock { Get-Content -Path (Get-item 'c:\Program Files\Microsoft Dynamics NAV\*\Service\CustomSettings.config').FullName })
#>

function Invoke-ScriptInBcContainer {
    Param (
        [string] $containerName = $bcContainerHelperConfig.defaultContainerName, 
        [Parameter(Mandatory=$true)]
        [ScriptBlock] $scriptblock,
        [Parameter(Mandatory=$false)]
        [Object[]] $argumentList,
        [bool] $useSession = $bcContainerHelperConfig.usePsSession
    )

    if ($useSession) {
        try {
            $session = Get-BcContainerSession -containerName $containerName -silent
        }
        catch {
            $useSession = $false
        }
    }
    if ($useSession) {
        $startTime = [DateTime]::Now
        try {
            Invoke-Command -Session $session -ScriptBlock $scriptblock -ArgumentList $argumentList
        }
        catch {
            $errorMessage = $_.Exception.Message
            Write-Host -ForegroundColor Red $errorMessage
            Write-Host
            Write-Host "Exception Script Stack Trace:"
            Write-Host -ForegroundColor Red $_.scriptStackTrace
            Write-Host
            Write-Host "PowerShell Call Stack:"
            Get-PSCallStack | Write-Host -ForegroundColor Red
            try {
               $isOutOfMemory = Invoke-Command -Session $session -ScriptBlock { Param($containerName, $startTime)
                    $cimInstance = Get-CIMInstance Win32_OperatingSystem
                    Write-Host "`nContainer Free Physical Memory: $(($cimInstance.FreePhysicalMemory/1024/1024).ToString('F1',[CultureInfo]::InvariantCulture))Gb"
                    $any = $false
                    Write-Host "`nServices in container $($containerName):"
                    Get-Service |
                        Where-Object { $_.Name -like "MicrosoftDynamics*" -or $_.Name -like "MSSQL`$*" } |
                        Select-Object -Property name, Status |
                        ForEach-Object {
                            if ($_.Status -eq "Running") {
                                Write-Host "- $($_.Name) is $($_.Status)"
                            }
                            else {
                                Write-Host -ForegroundColor Red "- $($_.Name) is $($_.Status)"
                            }
                            $any = $true
                        }
                    if (!$any) { Write-Host -ForegroundColor Red "- No services found" }
                    Write-Host
                    $any = $false
                    $isOutOfMemory = $false
                    Get-EventLog -LogName Application | 
                        Where-Object { $_.EntryType -eq "Error" -and $_.TimeGenerated -gt $startTime -and ($_.Source -like "MicrosoftDynamics*" -or $_.Source -like "MSSQL`$*") } | 
                        Select-Object -Property TimeGenerated, Source, Message |
                        ForEach-Object {
                            if (!$any) {
                                Write-Host "`nRelevant event log from container $($containerName):"
                            }
                            Write-Host -ForegroundColor Red "- $($_.TimeGenerated.ToString('yyyyMMdd hh:mm:ss')) - $($_.Source)"
                            Write-Host -ForegroundColor Gray "`n $($_.Message.Replace("`n","`n "))`n"
                            if ($_.Message.Contains('OutOfMemoryException')) { $isOutOfMemory = $true }
                            $any = $true
                        }
                    $isOutOfMemory
                } -ArgumentList $containerName, $startTime
                if ($isOutOfMemory) {
                    $errorMessage = "Out Of Memory Exception thrown inside container $containerName"
                }
            } catch {}
            throw $errorMessage
        }
    } else {
        $file = Join-Path $containerHelperFolder ([GUID]::NewGuid().Tostring()+'.ps1')
        $outputFile = "$file.output"
        try {
            if ($argumentList) {
                $encryptionKey = $null
                $xml = [xml]([System.Management.Automation.PSSerializer]::Serialize($argumentList))
                $nsmgr = New-Object System.Xml.XmlNamespaceManager -ArgumentList $xml.NameTable
                $nsmgr.AddNamespace("ns", "http://schemas.microsoft.com/powershell/2004/04");  
                $nodes = $xml.SelectNodes("//ns:SS", $nsmgr)
                if ($nodes.Count -gt 0) {
                    $encryptionKey = New-Object Byte[] 16
                    [Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($encryptionKey)
                    '$encryptionkey = [System.Management.Automation.PSSerializer]::Deserialize('''+([xml]([System.Management.Automation.PSSerializer]::Serialize($encryptionKey))).OuterXml+''')' | Add-Content $file
                }
                foreach($node in $nodes) {
                    $node.InnerText = ConvertFrom-SecureString -SecureString ($node.InnerText | ConvertTo-SecureString) -Key $encryptionkey
                }
                
                $xmlbytes =[System.Text.Encoding]::UTF8.GetBytes($xml.OuterXml)
                '$xmlbytes = [Convert]::FromBase64String('''+[Convert]::ToBase64String($xmlbytes)+''')' | Add-Content $file
                '$xml = [xml]([System.Text.Encoding]::UTF8.GetString($xmlbytes))' | Add-Content $file

                if ($encryptionKey) {
                    '$nsmgr = New-Object System.Xml.XmlNamespaceManager -ArgumentList $xml.NameTable' | Add-Content $file
                    '$nsmgr.AddNamespace("ns", "http://schemas.microsoft.com/powershell/2004/04")' | Add-Content $file
                    '$nodes = $xml.SelectNodes("//ns:SS", $nsmgr)' | Add-Content $file
                    'foreach($node in $nodes) { $node.InnerText = ConvertFrom-SecureString -SecureString ($node.InnerText | ConvertTo-SecureString -Key $encryptionKey) }' | Add-Content $file
                }
                '$argumentList = [System.Management.Automation.PSSerializer]::Deserialize($xml.OuterXml)' | Add-Content $file
            }

'$runPath = "c:\Run"
$myPath = Join-Path $runPath "my"
 
function Get-MyFilePath([string]$FileName)
{
    if ((Test-Path $myPath -PathType Container) -and (Test-Path (Join-Path $myPath $FileName) -PathType Leaf)) {
        (Join-Path $myPath $FileName)
    } else {
        (Join-Path $runPath $FileName)
    }
}
 
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
 
. (Get-MyFilePath "prompt.ps1") -silent | Out-Null
. (Get-MyFilePath "ServiceSettings.ps1") | Out-Null
. (Get-MyFilePath "HelperFunctions.ps1") | Out-Null
 
$txt2al = ""
if ($roleTailoredClientFolder) {
    $txt2al = Join-Path $roleTailoredClientFolder "txt2al.exe"
    if (!(Test-Path $txt2al)) {
        $txt2al = ""
    }
}
 
Set-Location $runPath
'
 | Add-Content $file

            '$result = Invoke-Command -ScriptBlock {' + $scriptblock.ToString() + '} -ArgumentList $argumentList' | Add-Content $file
            'if ($result) { [System.Management.Automation.PSSerializer]::Serialize($result) | Set-Content "'+$outputFile+'" }' | Add-Content $file

            docker exec $containerName powershell $file | Out-Host
            if($LASTEXITCODE -ne 0) {
                Remove-Item $file -Force -ErrorAction SilentlyContinue
                Remove-Item $outputFile -Force -ErrorAction SilentlyContinue
                throw
            }
            if (Test-Path -Path $outputFile -PathType Leaf) {
                [System.Management.Automation.PSSerializer]::Deserialize((Get-content $outputFile))
            }
        } finally {
            Remove-Item $file -Force -ErrorAction SilentlyContinue
            Remove-Item $outputFile -Force -ErrorAction SilentlyContinue
        }
    }
}
Set-Alias -Name Invoke-ScriptInNavContainer -Value Invoke-ScriptInBcContainer
Export-ModuleMember -Function Invoke-ScriptInBcContainer -Alias Invoke-ScriptInNavContainer