From f9057147b22d03fba91b308f50aef75f5a6aa124 Mon Sep 17 00:00:00 2001 From: Freddy Kristiansen Date: Mon, 22 Apr 2024 10:57:59 +0200 Subject: [PATCH] Use sessions (#3496) Add PowerShell configurations and PSRemoting when creating containers Allow standard Container sessions (when host is running PS5 in admin mode) to be to PS5 or PS7 (base on version and usePwshForBC24 flag) Use WinRm to create a session to a container when running PS7 or non-admin mode. WinRm session uses HTTPS unless $bccontainerHelperConfig.useSslForWinRm is set to $false Using HTTP for WinRm requires you to add trustedHosts to the hosts winrm configuration (if not running as administrator) --------- Co-authored-by: freddydk Co-authored-by: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> --- BC.HelperFunctions.ps1 | 4 +- ContainerHandling/Enter-NavContainer.ps1 | 2 +- ContainerHandling/Get-NavContainerSession.ps1 | 40 ++++++----- .../Invoke-ScriptInNavContainer.ps1 | 38 ++++------- ContainerHandling/New-NavContainer.ps1 | 67 +++++++++++++++++++ ContainerHandling/Remove-NavContainer.ps1 | 12 ++++ 6 files changed, 120 insertions(+), 43 deletions(-) diff --git a/BC.HelperFunctions.ps1 b/BC.HelperFunctions.ps1 index 893a5bf79..5e7504d42 100644 --- a/BC.HelperFunctions.ps1 +++ b/BC.HelperFunctions.ps1 @@ -15,7 +15,9 @@ function Get-ContainerHelperConfig { "genericImageNameFilesOnly" = 'mcr.microsoft.com/businesscentral:{1}-filesonly' "usePsSession" = $true "usePwshForBc24" = $true - "tryWinRmSession" = !$isAdministrator + "useSslForWinRmSession" = $true + "tryWinRmSession" = $isPsCore -or !$isAdministrator + "alwaysUseWinRmSession" = $false "addTryCatchToScriptBlock" = $true "killPsSessionProcess" = $false "useVolumes" = $false diff --git a/ContainerHandling/Enter-NavContainer.ps1 b/ContainerHandling/Enter-NavContainer.ps1 index ba0607817..b024fbd28 100644 --- a/ContainerHandling/Enter-NavContainer.ps1 +++ b/ContainerHandling/Enter-NavContainer.ps1 @@ -19,7 +19,7 @@ function Enter-BcContainer { Process { if ($bcContainerHelperConfig.usePsSession) { - $session = Get-BcContainerSession -containerName $containerName -silent -tryWinRmSession + $session = Get-BcContainerSession -containerName $containerName -silent Enter-PSSession -Session $session if ($session.ComputerType -eq 'Container') { Invoke-Command -Session $session -ScriptBlock { diff --git a/ContainerHandling/Get-NavContainerSession.ps1 b/ContainerHandling/Get-NavContainerSession.ps1 index f4a2bc5fe..5daffa3ef 100644 --- a/ContainerHandling/Get-NavContainerSession.ps1 +++ b/ContainerHandling/Get-NavContainerSession.ps1 @@ -17,6 +17,8 @@ function Get-BcContainerSession { Param ( [string] $containerName = $bcContainerHelperConfig.defaultContainerName, [switch] $tryWinRmSession = $bccontainerHelperConfig.tryWinRmSession, + [switch] $alwaysUseWinRmSession = $bccontainerHelperConfig.alwaysUseWinRmSession, + [switch] $usePwsh = $bccontainerHelperConfig.usePwshForBc24, [switch] $silent, [switch] $reinit ) @@ -37,34 +39,40 @@ function Get-BcContainerSession { } } if (!$session) { + [System.Version]$platformVersion = Get-BcContainerPlatformVersion -containerOrImageName $containerName + if ($platformVersion -lt [System.Version]"24.0.0.0") { + $usePwsh = $false + } + $configurationName = 'Microsoft.PowerShell' + if ($usePwsh) { + $configurationName = 'PowerShell.7' + } if ($isInsideContainer) { $session = New-PSSession -Credential $bcContainerHelperConfig.WinRmCredentials -ComputerName $containerName -Authentication Basic -UseSSL -SessionOption (New-PSSessionOption -SkipCACheck -SkipCNCheck) } - elseif ($isAdministrator) { + elseif ($isAdministrator -and !$bcContainerHelperConfig.alwaysUseWinRmSession) { try { $containerId = Get-BcContainerId -containerName $containerName - $session = New-PSSession -ContainerId $containerId -RunAsAdministrator -ErrorAction SilentlyContinue + $session = New-PSSession -ContainerId $containerId -RunAsAdministrator -ErrorAction SilentlyContinue -ConfigurationName $configurationName } catch {} } if (!$session) { - if (!$tryWinRmSession) { - throw "Unable to create a session for container $containerName (tryWinRmSession is false)" + if (!($alwaysUseWinRmSession -or $tryWinRmSession)) { + throw "Unable to create session for container $containerName (alwaysUseWinRmSession and tryWinRmSession are both false)" + } + $useSSL = $bcContainerHelperConfig.useSslForWinRmSession $UUID = (Get-CimInstance win32_ComputerSystemProduct).UUID $credential = New-Object PSCredential -ArgumentList 'winrm', (ConvertTo-SecureString -string $UUID -AsPlainText -force) - Invoke-ScriptInBcContainer -containerName $containerName -useSession:$false -scriptblock { Param([PSCredential] $credential) - $winrmuser = get-localuser -name $credential.UserName -ErrorAction SilentlyContinue - if (!$winrmuser) { - $cert = New-SelfSignedCertificate -DnsName "dontcare" -CertStoreLocation Cert:\LocalMachine\My - winrm create winrm/config/Listener?Address=*+Transport=HTTPS ('@{Hostname="dontcare"; CertificateThumbprint="' + $cert.Thumbprint + '"}') | Out-Null - winrm set winrm/config/service/Auth '@{Basic="true"}' | Out-Null - Write-Host "`nCreating Container user $($credential.UserName)" - New-LocalUser -AccountNeverExpires -PasswordNeverExpires -FullName $credential.UserName -Name $credential.UserName -Password $credential.Password | Out-Null - Add-LocalGroupMember -Group administrators -Member $credential.UserName | Out-Null - } - } -argumentList $credential - $session = New-PSSession -Credential $credential -ComputerName $containerName -Authentication Basic -useSSL -SessionOption (New-PSSessionOption -SkipCACheck -SkipCNCheck) + if ($useSSL) { + $sessionOption = New-PSSessionOption -Culture 'en-US' -UICulture 'en-US' -SkipCACheck -SkipCNCheck + $Session = New-PSSession -ConnectionUri "https://$($containerName):5986" -Credential $credential -Authentication Basic -SessionOption $sessionOption -ConfigurationName $configurationName + } + else { + $sessionOption = New-PSSessionOption -Culture 'en-US' -UICulture 'en-US' + $Session = New-PSSession -ConnectionUri "http://$($containerName):5985" -Credential $credential -Authentication Basic -SessionOption $sessionOption -ConfigurationName $configurationName + } } $newsession = $true } diff --git a/ContainerHandling/Invoke-ScriptInNavContainer.ps1 b/ContainerHandling/Invoke-ScriptInNavContainer.ps1 index bee7bff7a..c63a11241 100644 --- a/ContainerHandling/Invoke-ScriptInNavContainer.ps1 +++ b/ContainerHandling/Invoke-ScriptInNavContainer.ps1 @@ -31,39 +31,20 @@ function Invoke-ScriptInBcContainer { ) $file = Join-Path $bcContainerHelperConfig.hostHelperFolder ([GUID]::NewGuid().Tostring()+'.ps1') - $containerFile = "" - $shell = 'powershell' - if ($usePwsh) { - [System.Version]$platformVersion = Get-BcContainerPlatformVersion -containerOrImageName $containerName - if ($platformVersion -ge [System.Version]"24.0.0.0") { - $useSession = $false - $shell = 'pwsh' - } - else { - $usePwsh = $false - } - } - if (-not $usePwsh) { - if ($isInsideContainer) { - $useSession = $true - } - else { - $containerFile = Get-BcContainerPath -containerName $containerName -path $file - if ("$containerFile" -eq "") { - $useSession = $true - } - } + $containerFile = $containerFile = Get-BcContainerPath -containerName $containerName -path $file + if ($isInsideContainer -or "$containerFile" -eq "") { + $useSession = $true } if ($useSession) { try { - $session = Get-BcContainerSession -containerName $containerName -silent + $session = Get-BcContainerSession -containerName $containerName -silent -usePwsh:$usePwsh } catch { if ($isInsideContainer) { Write-Host "Error trying to establish session, retrying in 5 seconds" Start-Sleep -Seconds 5 - $session = Get-BcContainerSession -containerName $containerName -silent + $session = Get-BcContainerSession -containerName $containerName -silent -usePwsh:$usePwsh } else { $useSession = $false @@ -129,7 +110,14 @@ function Invoke-ScriptInBcContainer { } } else { if ("$containerFile" -eq "") { - $containerFile = Get-BcContainerPath -containerName $containerName -path $file -throw + throw "$($bcContainerHelperConfig.hostHelperFolder) is not shared with the container, cannot invoke scripts in container without using a session" + } + $shell = 'powershell' + if ($usePwsh) { + [System.Version]$platformVersion = Get-BcContainerPlatformVersion -containerOrImageName $containerName + if ($platformVersion -ge [System.Version]"24.0.0.0") { + $shell = 'pwsh' + } } $hostOutputFile = "$file.output" $containerOutputFile = "$containerFile.output" diff --git a/ContainerHandling/New-NavContainer.ps1 b/ContainerHandling/New-NavContainer.ps1 index b26336afc..4f833613a 100644 --- a/ContainerHandling/New-NavContainer.ps1 +++ b/ContainerHandling/New-NavContainer.ps1 @@ -1597,8 +1597,75 @@ if (!$restartingInstance) { Add-LocalGroupMember -Group administrators -Member '+$bcContainerHelperConfig.WinRmCredentials.UserName+' } ') | Add-Content -Path "$myfolder\AdditionalSetup.ps1" + } + else { + $UUID = (Get-CimInstance win32_ComputerSystemProduct).UUID + (' +if (!$restartingInstance) { + Write-Host "Enable PSRemoting and setup user for winrm" + Enable-PSRemoting | Out-Null + Get-PSSessionConfiguration | Out-null + pwsh.exe -Command "Enable-PSRemoting -WarningAction SilentlyContinue | Out-Null; Get-PSSessionConfiguration | Out-Null" + $credential = New-Object PSCredential -ArgumentList "winrm", (ConvertTo-SecureString -string "'+$UUID+'" -AsPlainText -force) + New-LocalUser -AccountNeverExpires -PasswordNeverExpires -FullName $credential.UserName -Name $credential.UserName -Password $credential.Password | Out-Null + Add-LocalGroupMember -Group administrators -Member $credential.UserName | Out-Null + winrm set winrm/config/service/Auth ''@{Basic="true"}'' | Out-Null +} +') | Add-Content -Path "$myfolder\AdditionalSetup.ps1" + if ($bccontainerHelperConfig.useSslForWinRmSession) { + $additionalParameters += @("--expose 5986") + (' +if (!$restartingInstance) { + Write-Host "Creating self-signed certificate for winrm" + $cert = New-SelfSignedCertificate -CertStoreLocation cert:\localmachine\my -DnsName $env:computername -NotBefore (get-date).AddDays(-1) -NotAfter (get-date).AddYears(5) -Provider "Microsoft RSA SChannel Cryptographic Provider" -KeyLength 2048 + winrm create winrm/config/Listener?Address=*+Transport=HTTPS ("@{Hostname=""$env:computername""; CertificateThumbprint=""$($cert.Thumbprint)""}") | Out-Null +} +') | Add-Content -Path "$myfolder\AdditionalSetup.ps1" + } + else { + $additionalParameters += @("--expose 5985") + (' +if (!$restartingInstance) { + Write-Host "Allow unencrypted communication to container" + winrm set winrm/config/service ''@{AllowUnencrypted="true"}'' | Out-Null +} +') | Add-Content -Path "$myfolder\AdditionalSetup.ps1" + } + if (-not $bccontainerHelperConfig.useSslForWinRmSession) { + try { + [xml]$conf = winrm get winrm/config/client -format:pretty + $trustedHosts = @($conf.Client.TrustedHosts.Split(',')) + if (-not $trustedHosts) { + $trustedHosts = @() + } + $isTrusted = $trustedHosts | Where-Object { $containerName -like $_ } + if (!($isTrusted)) { + if (!$isAdministrator) { + Write-Host "$containerName is not a trusted host. You need to get an administrator to add $containerName to the trusted winrm hosts on your machine" + } + else { + Write-Host "Adding $containerName to trusted hosts ($($trustedHosts -join ','))" + $trustedHosts += $containerName + winrm set winrm/config/client "@{TrustedHosts=""$($trustedHosts -join ',')""}" | Out-Null + } + } + if ($conf.Client.AllowUnencrypted -eq 'false') { + if (!$isAdministrator) { + Write-Host "Unencrypted communication is not allowed. You need to get an administrator to allow unencrypted communication" + } + else { + Write-Host "Allow unencrypted communication" + winrm set winrm/config/client '@{AllowUnencrypted="true"}' | Out-Null + } + } + } + catch { + Write-Host "Unexpected error when checking winrm configuration, you might not be able to connect to the container using winrm unencrypted" + } + } } + if ($includeCSide) { $programFilesFolder = Join-Path $containerFolder "Program Files" New-Item -Path $programFilesFolder -ItemType Directory -ErrorAction Ignore | Out-Null diff --git a/ContainerHandling/Remove-NavContainer.ps1 b/ContainerHandling/Remove-NavContainer.ps1 index f3a918325..a12f06c29 100644 --- a/ContainerHandling/Remove-NavContainer.ps1 +++ b/ContainerHandling/Remove-NavContainer.ps1 @@ -62,6 +62,18 @@ try { . (Join-Path $PSScriptRoot "updatehosts.ps1") -hostsFile "c:\windows\system32\drivers\etc\hosts" -theHostname $tenantHostname -theIpAddress "" } + if ($isAdministrator) { + try { + [xml]$conf = winrm get winrm/config/client -format:pretty + $trustedHosts = $conf.Client.TrustedHosts.Split(',') + if ($trustedHosts -contains $containerName) { + Write-Host "Removing $containerName from trusted hosts ($($trustedHosts -join ','))" + winrm set winrm/config/client "@{TrustedHosts=""$(@($trustedHosts | Where-Object { $_ -ne $containerName }) -join ',')""}" | Out-Null + } + } + catch {} + } + if ($myVolume) { Write-Host "Removing volume $myVolumeName" docker volume remove $myVolumeName