Search code examples
dockerpowershellscriptingdocker-desktopwindows-11

Setup configuration of docker-desktop programatically via PowerShell Scripting


I'm here again, let's see if you can give me a hand...

Normally when you install docker-desktop from 0, some configuration files are set to:

C:\Users\AppData\Roaming\Docker\setting.json

It is almost always necessary to do manual steps: open the application, do the survey, then make changes to the settings, save and apply, then close... and log in again.

I am more into using docker-desktop from the terminal, I usually execute the commands to create the network and raise the containers based on docker-compose

The problem or rather the help I am looking for is if you can help me do the programmatic part of updating the settings and closing and reopening the desktop application: docker-desktop.

I tried doing something before coming here in C# but it turned out to be unstable:

Check/Wait for the invoked application to be open

I'm sure this could be achieved with PowerShell but I don't know much about it, In powershell i have done this:


# Defines the configuration dictionary
$settingsToReplace = @{
    '"exposeDockerAPIOnTCP2375": false,' = '"exposeDockerAPIOnTCP2375": true,'
    '"updateHostsFile": false,' = '"updateHostsFile": true,'
    '"licenseTermsVersion": 0,' = '"licenseTermsVersion": 2,'
}

# Defines the path to the configuration file
$settingsPath = "$env:APPDATA\Docker\settings.json"

# Read the contents of the configuration file
$settingsContent = Get-Content $settingsPath -Raw

# Replaces the values in the file content
foreach ($key in $settingsToReplace.Keys) {
    $settingsContent = $settingsContent -replace [regex]::Escape($key), $settingsToReplace[$key]
}

# Write the modified content back to the file
$settingsContent | Set-Content $settingsPath

# Close Docker Desktop
Stop-Process -Name "Docker Desktop*" -ErrorAction SilentlyContinue

# Wait until Docker Desktop has completely closed
$timeout = 60  # seconds
$processName = "Docker Desktop"
$timeoutReached = $false
$startTime = Get-Date

# Wait until Docker Desktop has completely closed or the timeout has been reached
while ((Get-Process -Name $processName -ErrorAction SilentlyContinue) -and (-not $timeoutReached)) {
    # Verifica si el proceso de Docker Desktop se ha cerrado
    if (-not (Get-Process -Name $processName -ErrorAction SilentlyContinue)) {
        Write-Host "Docker Desktop has closed before the time limit was reached."
        break
    }

    # Check if the time limit has been reached
    if ((Get-Date) - $startTime -ge [TimeSpan]::FromSeconds($timeout)) {
        $timeoutReached = $true
    }

    # Wait 1 second before checking again
    Start-Sleep -Seconds 1
}

# Check if the timeout has been reached
if ($timeoutReached) {
    Write-Host "Docker Desktop did not close properly. Please manually close the application and run the script again."
} else {
    Write-Host "Docker Desktop has closed successfully. Continuing..."
}

# Open Docker Desktop

$docker = [System.Environment]::GetEnvironmentVariable('ProgramFiles') + '\Docker\Docker\Docker Desktop.exe'
Start-Process -FilePath $docker

# Wait until Docker Desktop has fully opened
$processName = "Docker Desktop"
while (-not (Get-Process -Name $processName -ErrorAction SilentlyContinue)) {
    Start-Sleep -Seconds 1
}

Write-Host "continue"

But i detect that docker-desktop become unstable again... and the setting update file is applied to setting.json but is not charged to docker-desktop when is reopen..

Docker post forum related: https://forums.docker.com/t/programmatic-manipulation-and-management-of-docker-desktop/140018

Update:

What are the exact symptoms of "instability"? answer: When opening Docker-Desktop using the script, the Docker-Desktop User Interface slowly slows down to the point that it are blocked, no response to mouse clicks.

What And how do you test if the changes have been applied after restarting the Docker desktop? answer: the settings tab is opened in docker-desktop and they continue as if the changes had not been made. but the changes are reflected in the setting.json file.

Update #2

Let's summarize what I'm doing, I've left C# aside to evaluate what makes docker desktop unstable, and this is the complete powershell script I'm deploying, with the changes I need in setting.json, the script works correctly, but once that docker-desktop UI opens after 30 seconds, it is completely unstable / somne flicker when try to click any UI control:

$ErrorActionPreference = 'Continue'
Set-Service -Name "com.docker.service" -StartupType Automatic -ErrorAction SilentlyContinue
Stop-Service -Name "com.docker.service" -ErrorAction SilentlyContinue
Get-Process 'Docker Desktop' -ErrorAction Ignore | Stop-Process -Force -ErrorAction Stop
Wait-Process -Name 'Docker Desktop' -ErrorAction SilentlyContinue
$settingsToUpdate = @{
    exposeDockerAPIOnTCP2375 = $true
    updateHostsFile = $true
    licenseTermsVersion = 2
    noWindowsContainers = $true
    runWinServiceInWslMode = $false
    useResourceSaver = $false
}

$settingsPath = "$env:APPDATA\Docker\settings.json"
$settingsContent = Get-Content $settingsPath -Raw 
$settingsObject  = $settingsContent | ConvertFrom-Json

$trackUpdates = 0
foreach ($update in $settingsToUpdate.GetEnumerator()) {
    if ($target = $settingsObject.psobject.Properties.Match($update.Key)) {
        if ($target.Value -ne $update.Value) {
            Add-Member -InputObject $settingsObject -MemberType NoteProperty -Name $update.Key -Value $update.Value -Force
            $trackUpdates++
        }
    }
}

if ($trackUpdates -eq 0) {
    Write-Host "No new settings applied"
} else {
    $settingsObject | ConvertTo-Json | Set-Content $settingsPath
    Write-Host "Settings updated and saved successfully"
}

Start-Service -Name "com.docker.service" -ErrorAction SilentlyContinue

while ((Get-Service -Name "com.docker.service").Status -ne "Running") {
    Write-Host (Get-Service -Name "com.docker.service").Status
    Start-Sleep -Seconds 1
}

if((Get-Service -Name "com.docker.service").Status -eq "Running"){
    Write-Host (Get-Service -Name "com.docker.service").Status
}

$dockerDesktopFilePath = $env:ProgramFiles | Join-Path -ChildPath 'Docker\Docker\Docker Desktop.exe'; Start-Process -FilePath $dockerDesktopFilePath

$ipcTimeout = New-TimeSpan -Seconds 20
$waitUntil = [datetime]::Now.Add($ipcTimeout)
$pipeOpen = $false
Write-Host 'Probing docker engine I/O pipe'
do {
  Start-Sleep -Milliseconds 100
  $pipeOpen = Test-Path -LiteralPath \\.\pipe\docker_engine
} until ($pipeOpen -or ($waitUntil -le [datetime]::Now))

if (-not $pipeOpen) {
  Write-Warning "Failed to observe named IPC pipe docker_engine within timeout"
  return
}

$responseTimeout = New-TimeSpan -Seconds 5
$waitUntil = [datetime]::Now.Add($responseTimeout)

Write-Host 'Querying docker server info'
do {
  Start-Sleep -Milliseconds 500
  $dockerInfoOutput = docker info 2>&1 
  $dockerInfoSuccess = $?
} until ($dockerInfoSuccess -or ($waitUntil -le [datetime]::Now))

if (-not $dockerInfoSuccess) {
  Write-Warning "docker info failed within timeout"
  return
}

Write-Host 'Docker Desktop Is Runing Now'

Update #3

So the problem is not how the application is opened, but how it is closed…

a friend try to run this script:

$ServiceName = "com.docker.service"
$ProcessName = "Docker Desktop"

$arrService = Get-Service -Name $ServiceName
if ($arrService.Status -ne 'Running') {
    Stop-Service -Name $ServiceName
}


if (Get-Process $ProcessName -ErrorAction SilentlyContinue) {  
  Stop-Process -Name $ProcessName
  Wait-Process -Name $ProcessName
}

In my case, the service would not be running, so the upper section of the code does not matter. In the task manager I could see that 4 or respectively 6 processes are running in relation to Docker desktop. So that the Stop-Process command terminated the porcess “Docker Desktop” immediately. But 2 processes “Docker Desktop Backend” and “Docker Desktop Extensions” are still running. I started Docker Desktop by double-clicking on a shortcut and could then see that the Docker Engine was paused and the application was not running stably. I then terminated all processes and was able to open Docker Desktop again without any problems. From here on, you have to find out everything else for yourself.

it is not clear to me how many processes have to be closed when closing Docker-Desktop, it seems that the closing requires a wild-card: "Docker-Desktop*" or "Docker Desktop*" or "*Docker*" but this can cause processes that should be kept running to close.

On the other hand I found something curious last night when I did installation #64 from 0.

It turns out that if you have docker running and you go to the settings for the first time and check the option to update the hosts file, the updateHostsFile property is not the only one that changes; These other 2 also change: noWindowsContainers, runWinServiceInWslMode and screw up the general docker configuration, if you are using Windows 11/10 home, since these options should only be enabled if you are using the PRO version with Hiper-V.

So for this reason, if you programmatically activate updateHostsFile it will have no effect… unless you activate the other two… which will not have positive results either…

The thing is complicated and complex… it should be simpler to manage this through PowerShell…


Solution

  • The final script to programaticaly:

    • Stop docker service and process.
    • Update setting.json.
    • Reopen docker desktop are here:

    https://gist.github.com/arcanisgk/d78acd5d51ab263d9467fb2da97781ca

    The problem of instability occurred because not all the processes that docker incorporates were closed, apparently there are a series of additional processes (not services) that must be closed equally or it will cause instability.

    $ErrorActionPreference = 'Continue'
    
    Stop-Service -Name "com.docker.*" -ErrorAction SilentlyContinue
    Set-Service -Name "com.docker.service" `
      -StartupType Automatic `
      -ErrorAction SilentlyContinue
    
    $processesToStop = @(
      'Docker Desktop',
      'com.docker.backend',
      'com.docker.extensions'
    )
    $processesToStop | ForEach-Object {
      Get-Process -Name $_ -ErrorAction Ignore |
        Stop-Process -Force -ErrorAction Ignore
    }
    
    $settingsToUpdate = @{
      exposeDockerAPIOnTCP2375 = $true
      updateHostsFile = $true
      licenseTermsVersion = 2
      noWindowsContainers = $false    # Required to enable read updateHostsFile
      runWinServiceInWslMode = $true  # Required to enable read updateHostsFile
      useResourceSaver = $false
    }
    
    $settingsPath    = "$env:APPDATA\Docker\settings.json"
    $settingsContent = Get-Content $settingsPath -Raw 
    $settingsObject  = $settingsContent | ConvertFrom-Json
    
    $trackUpdates = 0
    foreach ($update in $settingsToUpdate.GetEnumerator()) {
      if ($target = $settingsObject.psobject.Properties.Match($update.Key)) {
        if ($target.Value -ne $update.Value) {
          Write-Host $update.Key
          Add-Member `
            -InputObject $settingsObject `
            -MemberType NoteProperty `
            -Name $update.Key `
            -Value $update.Value `
            -Force
          $trackUpdates++
        }
      }
    }
    
    if ($trackUpdates -eq 0) {
      Write-Host "No new settings applied"
    } else {
      $settingsObject | ConvertTo-Json | Set-Content $settingsPath
      Write-Host "Settings updated and saved successfully"
    }
    
    Start-Service -Name "com.docker.service" -ErrorAction SilentlyContinue
    
    while ((Get-Service -Name "com.docker.service").Status -ne "Running") {
      Write-Host (Get-Service -Name "com.docker.service").Status
      Start-Sleep -Seconds 1
    }
    
    if((Get-Service -Name "com.docker.service").Status -eq "Running"){
      Write-Host "Service: com.docker.service is now:"
      Write-Host (Get-Service -Name "com.docker.service").Status
    }
    
    $dockerDesktopFilePath = $env:ProgramFiles |
      Join-Path -ChildPath 'Docker\Docker\Docker Desktop.exe'
    Start-Process -FilePath $dockerDesktopFilePath
    
    $ipcTimeout = New-TimeSpan -Seconds 20
    $waitUntil = [datetime]::Now.Add($ipcTimeout)
    $pipeOpen = $false
    Write-Host 'Probing docker engine I/O pipe'
    do {
      Start-Sleep -Milliseconds 100
      $pipeOpen = Test-Path -LiteralPath \\.\pipe\docker_engine
    }
    until ($pipeOpen -or ($waitUntil -le [datetime]::Now))
    
    if (-not $pipeOpen) {
      Write-Warning "Failed to observe named IPC pipe docker_engine within timeout"
      return
    }
    
    $responseTimeout = New-TimeSpan -Seconds 5
    $waitUntil = [datetime]::Now.Add($responseTimeout)
    
    Write-Host 'Querying docker server info'
    do {
      Start-Sleep -Milliseconds 500
      $dockerInfoOutput = docker info 2>&1
      $dockerInfoSuccess = $?
    }
    until ($dockerInfoSuccess -or ($waitUntil -le [datetime]::Now))
    
    if (-not $dockerInfoSuccess) {
      Write-Warning "docker info failed within timeout"
      return
    }
    
    Write-Host 'Docker Desktop Is Runing Now'