Functions/GenXdev.AI/EnsureDockerDesktop.ps1
################################################################################ <# .SYNOPSIS Ensures Docker Desktop is installed and available for containerization operations. .DESCRIPTION Verifies if Docker Desktop is installed and properly configured on the system. If not found, installs Docker Desktop using WinGet and handles the complete installation process automatically. This function also manages Docker Desktop service startup, daemon readiness verification, and handles authentication requirements when necessary. .EXAMPLE EnsureDockerDesktop Ensures Docker Desktop is installed and properly configured. #> ############################################################################### function EnsureDockerDesktop { [CmdletBinding()] param() begin { ########################################################################### <# .SYNOPSIS Checks if the WinGet PowerShell module is installed. .DESCRIPTION Attempts to import the Microsoft.WinGet.Client module and verifies its presence by checking if the module is available after import attempt. .EXAMPLE IsWinGetInstalled Returns $true if WinGet module is available, $false otherwise. #> ########################################################################### function IsWinGetInstalled { # attempt to load the winget module silently without error output Microsoft.PowerShell.Core\Import-Module "Microsoft.WinGet.Client" ` -ErrorAction SilentlyContinue # verify if module was loaded successfully by checking module list $module = Microsoft.PowerShell.Core\Get-Module "Microsoft.WinGet.Client" ` -ErrorAction SilentlyContinue # return true if module object exists, false otherwise return $null -ne $module } ########################################################################### <# .SYNOPSIS Installs the WinGet PowerShell module. .DESCRIPTION Installs and imports the Microsoft.WinGet.Client module for package management operations. Forces installation to override any conflicts. .EXAMPLE InstallWinGet Installs the WinGet PowerShell module from PowerShell Gallery. #> ########################################################################### function InstallWinGet { # output status message about winget installation Microsoft.PowerShell.Utility\Write-Verbose ("Installing WinGet " + "PowerShell client...") # install winget module with force to ensure success and allow clobber $null = PowerShellGet\Install-Module "Microsoft.WinGet.Client" ` -Force ` -AllowClobber # load the newly installed module into current session Microsoft.PowerShell.Core\Import-Module "Microsoft.WinGet.Client" } } process { # verify if docker desktop executable is available in current session if (@(Microsoft.PowerShell.Core\Get-Command 'docker.exe' ` -ErrorAction SilentlyContinue).Length -eq 0) { # define common docker installation paths for system and user installs $dockerPaths = @( "${env:ProgramFiles}\Docker\Docker\resources\bin", "${env:LOCALAPPDATA}\Programs\Docker\Docker\resources\bin" ) # initialize flag to track if docker was found in known paths $dockerFound = $false # iterate through each potential docker installation path foreach ($path in $dockerPaths) { # check if docker executable exists in current path if (Microsoft.PowerShell.Management\Test-Path ` (Microsoft.PowerShell.Management\Join-Path $path "docker.exe")) { # get current user PATH environment variable $currentPath = [Environment]::GetEnvironmentVariable('PATH', 'User') # add docker path to user PATH if not already present if ($currentPath -notlike "*$path*") { # inform user about path modification Microsoft.PowerShell.Utility\Write-Verbose ("Adding " + "Docker to system PATH...") # update user PATH environment variable permanently [Environment]::SetEnvironmentVariable( 'PATH', "$currentPath;$path", 'User') # update current session's path for immediate availability $env:PATH = [Environment]::GetEnvironmentVariable('PATH', 'User') } # mark docker as found and exit loop $dockerFound = $true break } } # install docker if not found in known installation paths if (-not $dockerFound) { # inform user about docker installation process Microsoft.PowerShell.Utility\Write-Host ("Docker Desktop not " + "found. Installing Docker Desktop...") # ensure winget is available before attempting docker installation if (-not (IsWinGetInstalled)) { InstallWinGet } # install docker desktop using winget package manager $null = Microsoft.WinGet.Client\Install-WinGetPackage ` -Id 'Docker.DockerDesktop' ` -Force # re-check docker paths after installation to update PATH foreach ($path in $dockerPaths) { # verify docker executable exists in path after installation if (Microsoft.PowerShell.Management\Test-Path ` (Microsoft.PowerShell.Management\Join-Path $path ` "docker.exe")) { # get current user PATH environment variable $currentPath = [Environment]::GetEnvironmentVariable( 'PATH', 'User') # add docker path to PATH if not already present if ($currentPath -notlike "*$path*") { # inform user about path update after installation Microsoft.PowerShell.Utility\Write-Verbose ("Adding " + "Docker to system PATH...") # update user PATH environment variable [Environment]::SetEnvironmentVariable( 'PATH', "$currentPath;$path", 'User') # update current session's path immediately $env:PATH = [Environment]::GetEnvironmentVariable( 'PATH', 'User') } break } } # verify docker installation was successful by checking command if (-not (Microsoft.PowerShell.Core\Get-Command 'docker.exe' ` -ErrorAction SilentlyContinue)) { throw "Docker Desktop installation failed." } } } # check if docker desktop process is currently running $dockerDesktopProcess = Microsoft.PowerShell.Management\Get-Process ` "Docker Desktop" ` -ErrorAction SilentlyContinue # start docker desktop if process is not running if (-not $dockerDesktopProcess) { # inform user about docker desktop startup Microsoft.PowerShell.Utility\Write-Host "Starting Docker Desktop..." # try to find docker desktop executable via get-command $dockerExePath = Microsoft.PowerShell.Core\Get-Command ` "Docker Desktop.exe" ` -ErrorAction SilentlyContinue # start docker desktop if found via get-command if ($dockerExePath) { # start docker desktop process using found executable path Microsoft.PowerShell.Management\Start-Process $dockerExePath.Source # wait for docker desktop to initialize (30 seconds) Microsoft.PowerShell.Utility\Start-Sleep 30 } else { # define common docker desktop executable paths $dockerDesktopPaths = @( "${env:ProgramFiles}\Docker\Docker\Docker Desktop.exe", ("${env:LOCALAPPDATA}\Programs\Docker\Docker\" + "Docker Desktop.exe") ) # try each known docker desktop installation path foreach ($path in $dockerDesktopPaths) { # check if docker desktop executable exists at current path if (Microsoft.PowerShell.Management\Test-Path $path) { # start docker desktop from found path Microsoft.PowerShell.Management\Start-Process $path # wait for docker desktop to initialize Microsoft.PowerShell.Utility\Start-Sleep 30 break } } } } # wait for docker daemon to become ready for commands Microsoft.PowerShell.Utility\Write-Verbose ("Waiting for Docker " + "daemon to be ready...") # set timeout for waiting for docker daemon (60 seconds) $timeout = 60 # initialize elapsed time counter $elapsed = 0 # loop until docker daemon responds or timeout is reached do { # wait 2 seconds between docker daemon checks Microsoft.PowerShell.Utility\Start-Sleep -Seconds 2 # increment elapsed time counter $elapsed += 2 # attempt to get docker info to verify daemon is responding $dockerInfo = docker info 2>$null } while (-not $dockerInfo -and $elapsed -lt $timeout) # throw error if docker daemon failed to start within timeout if ($elapsed -ge $timeout) { throw ("Docker daemon failed to start within $timeout seconds.") } # initialize flag to track if docker login is required $loginRequired = $false # check if user needs to log in to docker desktop for registry access try { # verify docker daemon is responding to basic commands $dockerInfo = docker info --format '{{.Name}}' 2>$null # proceed with authentication check if daemon is responding if ($dockerInfo) { # output verbose message about daemon status Microsoft.PowerShell.Utility\Write-Verbose ("Docker daemon " + "is responding") # test registry access by attempting to pull hello-world image $testPull = docker pull hello-world:latest 2>&1 # output verbose message about pull test result Microsoft.PowerShell.Utility\Write-Verbose ("Test pull " + "result: $testPull") # check if pull failed due to authentication issues if ($testPull -match ("unauthorized|authentication required|" + "denied|login") -and $testPull -notmatch ("already exists|up to date|" + "downloaded newer")) { # output verbose message about detected authentication error Microsoft.PowerShell.Utility\Write-Verbose ("Authentication " + "error detected, login required") # set flag to require user login $loginRequired = $true } else { # output verbose message about successful authentication Microsoft.PowerShell.Utility\Write-Verbose ("No " + "authentication error detected, assuming logged in " + "or no login needed") # set flag to not require login $loginRequired = $false } } } catch { # if docker commands fail entirely, assume bigger issue than login $loginRequired = $false # output verbose message about docker command failure Microsoft.PowerShell.Utility\Write-Verbose ("Docker command " + "failed, assuming no login required: $($_.Exception.Message)") } # handle docker desktop login process if authentication is required if ($loginRequired) { # inform user about docker account requirement Microsoft.PowerShell.Utility\Write-Host ("Please create a Docker " + "account or log in to your existing Docker account in Docker " + "Desktop to continue.") -ForegroundColor Yellow # inform user about waiting for login Microsoft.PowerShell.Utility\Write-Host ("Waiting for Docker " + "Desktop login...") -ForegroundColor Cyan # set timeout for waiting for user login (5 minutes) $loginTimeout = 300 # initialize elapsed time counter for login wait $loginElapsed = 0 # initialize flag to track successful login $loginSuccessful = $false # loop until login is successful or timeout is reached do { # wait 5 seconds between login status checks Microsoft.PowerShell.Utility\Start-Sleep -Seconds 5 # increment elapsed time counter $loginElapsed += 5 # check multiple indicators of successful docker login try { # primary method: check docker info for username $dockerUsername = docker info --format '{{.Username}}' 2>$null # verify username indicates successful login if ($dockerUsername -and $dockerUsername -ne '<not set>' -and $dockerUsername.Trim().Length -gt 0) { $loginSuccessful = $true } # secondary method: try to authenticate with registry if (-not $loginSuccessful) { # attempt to get authentication token info $authCheck = docker system events --until=1s 2>$null # re-check username if auth check succeeded if ($authCheck -ne $null) { # re-check username after potential authentication $dockerUsername = docker info --format '{{.Username}}' 2>$null # verify username indicates successful login if ($dockerUsername -and $dockerUsername -ne '<not set>' -and $dockerUsername.Trim().Length -gt 0) { $loginSuccessful = $true } } } } catch { # continue waiting if docker commands fail } # provide progress feedback every 30 seconds if ($loginElapsed % 30 -eq 0 -and $loginElapsed -lt $loginTimeout) { # calculate remaining time and display progress message Microsoft.PowerShell.Utility\Write-Host ("Still waiting " + "for Docker Desktop login... " + "($($loginTimeout - $loginElapsed) seconds remaining)") ` -ForegroundColor Gray } } while (-not $loginSuccessful -and $loginElapsed -lt $loginTimeout) # handle login timeout or success if (-not $loginSuccessful) { # warn user about login timeout Microsoft.PowerShell.Utility\Write-Warning ("Docker Desktop " + "login timeout reached. Some Docker operations may " + "require manual authentication.") } else { # confirm successful login to user Microsoft.PowerShell.Utility\Write-Host ("✅ Docker Desktop " + "login detected.") -ForegroundColor Green } } # output final success message about docker desktop readiness Microsoft.PowerShell.Utility\Write-Verbose "✅ Docker Desktop is ready." } end { } } ################################################################################ |