diff --git a/Dockerfile b/Dockerfile
index 02686bf..708817b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -33,12 +33,18 @@ RUN apt update && apt install -y \
ca-certificates \
nmap \
libpcap-dev \
+ python3 \
+ python3-pip \
wget \
gnupg \
&& wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg \
&& echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list \
&& apt update \
&& apt install -y google-chrome-stable \
+ && POWERSHELL_VERSION=$(wget -qO- https://api.github.com/repos/PowerShell/PowerShell/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' | sed 's/v//') \
+ && wget -q -O /tmp/powershell.deb "https://github.com/PowerShell/PowerShell/releases/download/v${POWERSHELL_VERSION}/powershell_${POWERSHELL_VERSION}-1.deb_amd64.deb" \
+ && apt install -y /tmp/powershell.deb \
+ && rm /tmp/powershell.deb \
&& apt clean \
&& rm -rf /var/lib/apt/lists/*
diff --git a/README.md b/README.md
index 47600a2..611266a 100644
--- a/README.md
+++ b/README.md
@@ -31,11 +31,40 @@
### Installation
#### Go Install
+
+**Note:** Go must be pre-installed on your system. Download Go from [golang.org](https://golang.org/dl/) if needed.
+
+**Windows users:** You can use the automated setup script `setup.go.ps1` to install/update Go automatically.
+
```bash
go install github.com/projectdiscovery/pd-agent/cmd/pd-agent@latest
```
+The binary will be installed in your Go bin directory:
+- **Linux/macOS:** `$HOME/go/bin/pd-agent` (or `$GOPATH/bin/pd-agent` if `GOPATH` is set)
+- **Windows:** `%USERPROFILE%\go\bin\pd-agent.exe` (or `%GOPATH%\bin\pd-agent.exe` if `GOPATH` is set)
+
+Ensure this directory is in your PATH to run `pd-agent` (or `pd-agent.exe` on Windows) from anywhere.
+
+#### Download Binary from Releases
+
+Pre-built binaries are available for download from the [GitHub releases page](https://github.com/projectdiscovery/pd-agent/releases). Download the appropriate binary for your platform:
+
+- **Linux:** Download the `pd-agent-linux-amd64.tar.gz` archive, extract it, and place the `pd-agent` binary in a directory that is in your PATH (e.g., `/usr/local/bin`).
+- **macOS:** Download the `pd-agent-macos-amd64.tar.gz` archive, extract it, and place the `pd-agent` binary in a directory that is in your PATH (e.g., `/usr/local/bin`).
+- **Windows:** Download the `pd-agent-windows-amd64.exe` binary and place it in a directory that is in your PATH (e.g., `C:\Program Files\pd-agent`).
+
+Ensure the binary directory is in your system PATH to run `pd-agent` from anywhere.
+
#### Docker
+
+**Note:** Docker must be installed on your system. Download Docker from [docker.com](https://www.docker.com/products/docker-desktop/) if needed.
+
+**Windows users:** You can use the automated setup scripts:
+- `setup.windows.desktop.ps1` for Windows 10/11 (Docker Desktop)
+- `setup.windows.server.ps1` for Windows Server (Docker Engine from binaries)
+
+**Linux/macOS:**
```bash
docker run -d --name pd-agent \
--network host --cap-add NET_RAW --cap-add NET_ADMIN \
@@ -45,6 +74,61 @@ docker run -d --name pd-agent \
-agent-tags production
```
+**Windows (Docker Desktop):**
+```powershell
+docker run -d --name pd-agent \
+ -e PDCP_API_KEY=your-api-key \
+ -e PDCP_TEAM_ID=your-team-id \
+ projectdiscovery/pd-agent:latest \
+ -agent-tags production
+```
+
+**Note:** On Windows, `--network host` and `--cap-add` flags are not supported by Docker Desktop. Only passive discovery features are affected; all other agent functionality works normally.
+
+#### Automated Windows Setup Scripts
+
+We provide PowerShell scripts to automate the installation of prerequisites and the pd-agent on Windows:
+
+**Windows Desktop (Windows 10/11):**
+```powershell
+# Install Docker Desktop
+.\setup.windows.desktop.ps1
+
+# Install Go (optional)
+.\setup.go.ps1
+
+# Deploy pd-agent (installs Docker if needed, pulls image, and starts container)
+.\setup.agent.ps1
+```
+
+**Windows Server:**
+```powershell
+# Install Docker Engine from binaries (includes Windows Containers feature)
+.\setup.windows.server.ps1
+
+# Install Go (optional)
+.\setup.go.ps1
+
+# Deploy pd-agent (installs Docker if needed, pulls image, and starts container)
+.\setup.agent.ps1
+```
+
+**All-in-one deployment:**
+```powershell
+# Deploy pd-agent with automatic Docker and Go installation
+.\setup.agent.ps1 -InstallGo
+```
+
+**Features:**
+- Automatic Docker installation/update (Desktop or Server)
+- Optional Go installation/update
+- Automatic pd-agent image pull and container deployment
+- Preserves existing container configuration (env vars, volumes, commands)
+- Only updates if newer versions are available
+- Fully automated and silent installation
+
+**Note:** All scripts require Administrator privileges. Run PowerShell as Administrator before executing.
+
#### Kubernetes
```bash
# Create namespace
@@ -70,7 +154,7 @@ The agent automatically discovers Kubernetes cluster subnets (nodes, pods, servi
The agent automatically discovers local network subnets and reports them to the platform:
- **Local networks:** Discovers private IP ranges from network interfaces and routing tables
- **Kubernetes:** Automatically discovers and aggregates cluster subnets (node IPs, pod CIDRs, service CIDRs)
-- **Docker:** Use `--network host` and network capabilities (`NET_RAW`, `NET_ADMIN`) to enable discovery
+- **Docker (Linux/macOS only):** Use `--network host` and network capabilities (`NET_RAW`, `NET_ADMIN`) to enable passive discovery. On Windows Docker Desktop, these flags are not supported and passive discovery will be unavailable.
For Kubernetes deployments, the agent requires `ClusterRole` permissions to discover cluster resources (included in the deployment manifest).
@@ -84,13 +168,71 @@ For Kubernetes deployments, the agent requires `ClusterRole` permissions to disc
| `PDCP_AGENT_TAGS` | No | - | Comma-separated agent tags |
| `PDCP_AGENT_NAME` | No | Hostname | Agent display name |
+#### Setting Environment Variables
+
+**Linux/macOS (bash/zsh):**
+```bash
+export PDCP_API_KEY=your-api-key
+export PDCP_TEAM_ID=your-team-id
+export PDCP_AGENT_TAGS=production,staging
+```
+
+**Windows (PowerShell):**
+```powershell
+$env:PDCP_API_KEY="your-api-key"
+$env:PDCP_TEAM_ID="your-team-id"
+$env:PDCP_AGENT_TAGS="production,staging"
+```
+
+**Windows (Command Prompt):**
+```cmd
+set PDCP_API_KEY=your-api-key
+set PDCP_TEAM_ID=your-team-id
+set PDCP_AGENT_TAGS=production,staging
+```
+
+**Windows (Permanent - System Environment Variables):**
+1. Open System Properties → Advanced → Environment Variables
+2. Add variables under User or System variables
+3. Restart the terminal/service for changes to take effect
+
+**Windows (WSL2):**
+Follow Linux instructions - WSL2 uses Linux environment variable syntax.
+
### Usage
+**Linux/macOS:**
```bash
# Basic usage
pd-agent -agent-networks internal
+
+# With tags
+pd-agent -agent-tags production,staging -agent-networks internal
+```
+
+**Windows (PowerShell):**
+```powershell
+# Basic usage
+pd-agent.exe -agent-networks internal
+
+# With tags
+pd-agent.exe -agent-tags production,staging -agent-networks internal
+```
+
+**Windows (Command Prompt):**
+```cmd
+# Basic usage
+pd-agent.exe -agent-networks internal
+
+# With tags
+pd-agent.exe -agent-tags production,staging -agent-networks internal
```
+**Windows (WSL2):**
+Follow Linux instructions - commands are identical to Linux/macOS.
+
+**Note:** On Windows, if `pd-agent.exe` is in your PATH, you can use `pd-agent` instead of `pd-agent.exe`. The `.exe` extension is optional in PowerShell and required in Command Prompt.
+
### Configuration
The agent uses environment variables or command-line flags for configuration. See the Environment Variables table above for all available options.
@@ -125,13 +267,48 @@ The agent uses environment variables or command-line flags for configuration. Se
#### Enable Verbose Logging
-Add `-verbose` flag or set environment variable:
+**Linux/macOS:**
```bash
+# Using flag
+pd-agent -verbose
+
+# Using environment variable
export PDCP_VERBOSE=true
-# or
+pd-agent ...
+
+# Or inline
PDCP_VERBOSE=1 pd-agent ...
```
+**Windows (PowerShell):**
+```powershell
+# Using flag
+pd-agent.exe -verbose
+
+# Using environment variable
+$env:PDCP_VERBOSE="true"
+pd-agent.exe ...
+
+# Or inline
+$env:PDCP_VERBOSE="1"; pd-agent.exe ...
+```
+
+**Windows (Command Prompt):**
+```cmd
+# Using flag
+pd-agent.exe -verbose
+
+# Using environment variable
+set PDCP_VERBOSE=true
+pd-agent.exe ...
+
+# Or inline (requires separate commands)
+set PDCP_VERBOSE=1 && pd-agent.exe ...
+```
+
+**Windows (WSL2):**
+Follow Linux instructions - use Linux syntax.
+
### Best Practices
1. **Agent Tagging:** Use descriptive tags to organize agents (e.g., `production`, `staging`, `scanner-1`)
@@ -147,17 +324,32 @@ PDCP_VERBOSE=1 pd-agent ...
#### Custom Proxy Configuration
-Configure a custom proxy for agent communication:
-
+**Linux/macOS:**
```bash
export PROXY_URL=http://proxy.example.com:8080
pd-agent -verbose
```
+**Windows (PowerShell):**
+```powershell
+$env:PROXY_URL="http://proxy.example.com:8080"
+pd-agent.exe -verbose
+```
+
+**Windows (Command Prompt):**
+```cmd
+set PROXY_URL=http://proxy.example.com:8080
+pd-agent.exe -verbose
+```
+
+**Windows (WSL2):**
+Follow Linux instructions - use Linux syntax.
+
#### Agent Grouping
Use tags and networks to group agents:
+**Linux/macOS:**
```bash
# Production agents
pd-agent -agent-tags production,us-east -agent-networks prod-network
@@ -166,6 +358,27 @@ pd-agent -agent-tags production,us-east -agent-networks prod-network
pd-agent -agent-tags staging,us-west -agent-networks staging-network
```
+**Windows (PowerShell/Command Prompt):**
+```powershell
+# Production agents
+pd-agent.exe -agent-tags production,us-east -agent-networks prod-network
+
+# Staging agents
+pd-agent.exe -agent-tags staging,us-west -agent-networks staging-network
+```
+
+**Windows (WSL2):**
+Follow Linux instructions - commands are identical to Linux/macOS.
+
+### Docker Execution Note
+
+The Docker container comes with all prerequisites pre-installed and updated, requiring no further setup:
+
+- **ProjectDiscovery Tools:** dnsx, naabu, httpx, tlsx, nuclei
+- **System Tools:** nmap, Chrome (headless), PowerShell
+
+Nuclei executes headless templates and code templates automatically when the system has sufficient resources. Code templates can execute with the following interpreters on any platform: **bash**, **python**, and **PowerShell**.
+
--------
diff --git a/setup.agent.ps1 b/setup.agent.ps1
new file mode 100644
index 0000000..baad04a
--- /dev/null
+++ b/setup.agent.ps1
@@ -0,0 +1,398 @@
+#Requires -RunAsAdministrator
+
+<#
+.SYNOPSIS
+ Automated deployment and update script for pd-agent Docker container
+
+.DESCRIPTION
+ This script:
+ - Installs or updates Docker Desktop
+ - Optionally installs Go
+ - Pulls the latest pd-agent Docker image
+ - Stops idle running containers
+ - Starts a new container with the same configuration
+ - Skips update if image is already up to date
+#>
+
+param(
+ [switch]$InstallGo,
+ [string]$ContainerName = "pd-agent",
+ [string]$ImageName = "projectdiscovery/pd-agent:latest"
+)
+
+$ErrorActionPreference = "Stop"
+
+# Check if running as Administrator
+$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+if (-not $isAdmin) {
+ Write-Error "This script must be run as Administrator. Please run PowerShell as Administrator and try again."
+ exit 1
+}
+
+Write-Host "=== PD-Agent Deployment Script ===" -ForegroundColor Cyan
+Write-Host ""
+
+# Function to test if Docker is running
+function Test-DockerRunning {
+ try {
+ $null = docker version 2>$null
+ return ($LASTEXITCODE -eq 0)
+ } catch {
+ return $false
+ }
+}
+
+# Function to wait for Docker daemon
+function Wait-Docker {
+ Write-Host "Waiting for Docker daemon..." -ForegroundColor Cyan
+ $maxWaitMinutes = 2
+ $startTime = Get-Date
+
+ while (-not (Test-DockerRunning)) {
+ $timeElapsed = $(Get-Date) - $startTime
+ if ($timeElapsed.TotalMinutes -ge $maxWaitMinutes) {
+ throw "Docker daemon did not become ready within $maxWaitMinutes minutes."
+ }
+ Start-Sleep -Seconds 2
+ }
+ Write-Host "Docker daemon is ready." -ForegroundColor Green
+}
+
+# Install or update Docker
+Write-Host "[1/5] Checking Docker installation..." -ForegroundColor Cyan
+
+# Try to detect if we're on Windows Server or Desktop
+$isWindowsServer = $false
+try {
+ $osInfo = Get-CimInstance -ClassName Win32_OperatingSystem
+ if ($osInfo.ProductType -eq 3) {
+ $isWindowsServer = $true
+ }
+} catch {
+ # Assume desktop if we can't determine
+}
+
+# Choose appropriate Docker setup script
+if ($isWindowsServer) {
+ $dockerScript = Join-Path $PSScriptRoot "setup.windows.server.ps1"
+ Write-Host "Detected Windows Server. Using server binary installation." -ForegroundColor Gray
+} else {
+ $dockerScript = Join-Path $PSScriptRoot "setup.windows.desktop.ps1"
+ Write-Host "Detected Windows Desktop. Using Desktop installation." -ForegroundColor Gray
+}
+
+if (Test-Path $dockerScript) {
+ Write-Host "Running Docker setup script..." -ForegroundColor Gray
+ & $dockerScript
+ if ($LASTEXITCODE -ne 0) {
+ Write-Error "Docker setup failed. Please check the Docker setup script."
+ exit 1
+ }
+} else {
+ Write-Warning "Docker setup script not found at $dockerScript"
+ Write-Host "Checking if Docker is installed..." -ForegroundColor Cyan
+ if (-not (Test-DockerRunning)) {
+ Write-Error "Docker is not running. Please install Docker first or ensure the Docker setup script is in the same directory."
+ exit 1
+ }
+}
+
+Wait-Docker
+Write-Host "Docker is ready." -ForegroundColor Green
+Write-Host ""
+
+# Optionally install Go
+if ($InstallGo) {
+ Write-Host "[2/5] Installing/updating Go..." -ForegroundColor Cyan
+ $goScript = Join-Path $PSScriptRoot "setup.go.ps1"
+ if (Test-Path $goScript) {
+ Write-Host "Running Go setup script..." -ForegroundColor Gray
+ & $goScript
+ if ($LASTEXITCODE -ne 0) {
+ Write-Warning "Go setup had issues, but continuing with Docker deployment..."
+ }
+ } else {
+ Write-Warning "Go setup script not found at $goScript"
+ }
+ Write-Host ""
+} else {
+ Write-Host "[2/5] Skipping Go installation (use -InstallGo to enable)" -ForegroundColor Gray
+ Write-Host ""
+}
+
+# Check current image version
+Write-Host "[3/5] Checking current Docker image..." -ForegroundColor Cyan
+$currentImageId = $null
+$imageExists = $false
+
+try {
+ $imageInfo = docker images $ImageName --format "{{.ID}}" 2>$null
+ if ($imageInfo) {
+ $imageExists = $true
+ $currentImageId = $imageInfo.Trim()
+ Write-Host "Current image ID: $currentImageId" -ForegroundColor Gray
+ }
+} catch {
+ Write-Host "No local image found." -ForegroundColor Gray
+}
+
+# Pull latest image
+Write-Host "[4/5] Pulling latest pd-agent image..." -ForegroundColor Cyan
+try {
+ docker pull $ImageName
+ if ($LASTEXITCODE -ne 0) {
+ throw "Failed to pull Docker image"
+ }
+
+ $newImageId = (docker images $ImageName --format "{{.ID}}" 2>$null).Trim()
+
+ if ($imageExists -and $currentImageId -eq $newImageId) {
+ Write-Host "Image is already up to date (ID: $newImageId)" -ForegroundColor Green
+ Write-Host "Skipping container restart." -ForegroundColor Yellow
+ Write-Host ""
+ Write-Host "=== Deployment completed - no changes needed ===" -ForegroundColor Green
+ exit 0
+ } else {
+ if ($imageExists) {
+ Write-Host "New image pulled (old: $currentImageId, new: $newImageId)" -ForegroundColor Green
+ } else {
+ Write-Host "Image pulled successfully (ID: $newImageId)" -ForegroundColor Green
+ }
+ }
+} catch {
+ Write-Error "Failed to pull Docker image: $_"
+ exit 1
+}
+Write-Host ""
+
+# Handle existing container
+Write-Host "[5/5] Managing container..." -ForegroundColor Cyan
+
+$containerExists = $false
+$containerRunning = $false
+$containerId = $null
+
+try {
+ $containerInfo = docker ps -a --filter "name=$ContainerName" --format "{{.ID}}|{{.Status}}" 2>$null
+ if ($containerInfo) {
+ $containerExists = $true
+ $parts = $containerInfo -split '\|'
+ $containerId = $parts[0]
+ $status = $parts[1]
+
+ if ($status -like "*Up*") {
+ $containerRunning = $true
+ Write-Host "Container '$ContainerName' is running (ID: $containerId)" -ForegroundColor Yellow
+ } else {
+ Write-Host "Container '$ContainerName' exists but is stopped (ID: $containerId)" -ForegroundColor Gray
+ }
+ }
+} catch {
+ Write-Host "No existing container found." -ForegroundColor Gray
+}
+
+# Get original container configuration BEFORE stopping/removing
+Write-Host "Retrieving original container configuration..." -ForegroundColor Cyan
+
+$envVars = @()
+$volumes = @()
+$networkMode = $null
+$capAdd = @()
+$command = @()
+$restartPolicy = $null
+
+if ($containerId) {
+ try {
+ # Get container configuration before we stop/remove it
+ $inspectOutput = docker inspect $ContainerName 2>$null
+ if ($inspectOutput) {
+ $inspect = $inspectOutput | ConvertFrom-Json
+ if ($inspect) {
+ # Get environment variables
+ if ($inspect[0].Config.Env) {
+ $envVars = $inspect[0].Config.Env
+ }
+
+ # Get volumes
+ if ($inspect[0].Mounts) {
+ foreach ($mount in $inspect[0].Mounts) {
+ if ($mount.Type -eq "volume" -or $mount.Type -eq "bind") {
+ $volumes += "${($mount.Source)}:$($mount.Destination)"
+ }
+ }
+ }
+
+ # Get network mode
+ if ($inspect[0].HostConfig.NetworkMode) {
+ $networkMode = $inspect[0].HostConfig.NetworkMode
+ }
+
+ # Get capabilities
+ if ($inspect[0].HostConfig.CapAdd) {
+ $capAdd = $inspect[0].HostConfig.CapAdd
+ }
+
+ # Get restart policy
+ if ($inspect[0].HostConfig.RestartPolicy.Name) {
+ $restartPolicy = $inspect[0].HostConfig.RestartPolicy.Name
+ }
+
+ # Get command/args
+ if ($inspect[0].Config.Cmd) {
+ $command = $inspect[0].Config.Cmd
+ }
+ if ($inspect[0].Config.Entrypoint) {
+ $entrypoint = $inspect[0].Config.Entrypoint
+ }
+ }
+ }
+ } catch {
+ Write-Warning "Could not retrieve full container configuration: $_"
+ Write-Host "Will use default configuration." -ForegroundColor Yellow
+ }
+}
+
+# Stop and remove existing container
+if ($containerRunning) {
+ # Check if container is idle (low CPU/memory usage)
+ Write-Host "Checking if container is idle..." -ForegroundColor Cyan
+ $isIdle = $false
+
+ try {
+ $stats = docker stats $ContainerName --no-stream --format "{{.CPUPerc}}|{{.MemUsage}}" 2>$null
+ if ($stats) {
+ $parts = $stats -split '\|'
+ $cpuPerc = $parts[0] -replace '%', ''
+ $memUsage = $parts[1]
+
+ Write-Host "Container stats - CPU: $cpuPerc%, Memory: $memUsage" -ForegroundColor Gray
+
+ # Consider idle if CPU < 1% (can be adjusted)
+ if ([double]$cpuPerc -lt 1.0) {
+ $isIdle = $true
+ Write-Host "Container appears to be idle (CPU < 1%)." -ForegroundColor Yellow
+ } else {
+ Write-Host "Container is active (CPU: $cpuPerc%)." -ForegroundColor Yellow
+ }
+ } else {
+ Write-Warning "Could not get container stats. Assuming container is active."
+ }
+ } catch {
+ Write-Warning "Could not check container stats: $_"
+ Write-Warning "Assuming container is active."
+ }
+
+ # Stop container for update
+ if ($isIdle) {
+ Write-Host "Stopping idle container for update..." -ForegroundColor Yellow
+ } else {
+ Write-Host "Stopping active container for update..." -ForegroundColor Yellow
+ }
+ docker stop $ContainerName
+ Start-Sleep -Seconds 2
+}
+
+if ($containerExists) {
+ Write-Host "Removing old container..." -ForegroundColor Cyan
+ docker rm $ContainerName 2>$null
+ if ($LASTEXITCODE -eq 0) {
+ Write-Host "Old container removed." -ForegroundColor Green
+ }
+ Start-Sleep -Seconds 1
+}
+
+# Build docker run command and start new container
+Write-Host "Starting new container with configuration..." -ForegroundColor Cyan
+
+$dockerArgs = @("run", "-d", "--name", $ContainerName)
+
+# Add restart policy
+if ($restartPolicy) {
+ $dockerArgs += "--restart"
+ $dockerArgs += $restartPolicy
+} else {
+ $dockerArgs += "--restart"
+ $dockerArgs += "unless-stopped"
+}
+
+# Add environment variables
+if ($envVars.Count -gt 0) {
+ foreach ($env in $envVars) {
+ $dockerArgs += "-e"
+ $dockerArgs += $env
+ }
+} else {
+ # Default environment variables if none found
+ Write-Host "No environment variables found. Using defaults." -ForegroundColor Yellow
+ Write-Host "Note: You may need to set PDCP_API_KEY and PDCP_TEAM_ID manually." -ForegroundColor Yellow
+}
+
+# Add volumes
+if ($volumes.Count -gt 0) {
+ foreach ($vol in $volumes) {
+ $dockerArgs += "-v"
+ $dockerArgs += $vol
+ }
+}
+
+# Add network mode (Windows doesn't support host mode, so skip it)
+if ($networkMode -and $networkMode -ne "host") {
+ $dockerArgs += "--network"
+ $dockerArgs += $networkMode
+}
+
+# Add capabilities (Windows doesn't support these, but include for compatibility)
+if ($capAdd.Count -gt 0 -and $IsLinux) {
+ foreach ($cap in $capAdd) {
+ $dockerArgs += "--cap-add"
+ $dockerArgs += $cap
+ }
+}
+
+# Add image
+$dockerArgs += $ImageName
+
+# Add command/arguments
+if ($command.Count -gt 0) {
+ $dockerArgs += $command
+}
+
+# Execute docker run
+Write-Host "Executing: docker $($dockerArgs -join ' ')" -ForegroundColor Gray
+try {
+ $newContainerId = docker $dockerArgs 2>&1
+ if ($LASTEXITCODE -ne 0) {
+ throw "Failed to start container: $newContainerId"
+ }
+
+ $newContainerId = $newContainerId.Trim()
+ Write-Host "Container started successfully (ID: $newContainerId)" -ForegroundColor Green
+
+ # Wait a moment and verify
+ Start-Sleep -Seconds 2
+ $status = docker ps --filter "name=$ContainerName" --format "{{.Status}}" 2>$null
+ if ($status) {
+ Write-Host "Container status: $status" -ForegroundColor Green
+ }
+
+} catch {
+ Write-Error "Failed to start container: $_"
+ Write-Host ""
+ Write-Host "You may need to manually start the container with:" -ForegroundColor Yellow
+ Write-Host " docker run -d --name $ContainerName -e PDCP_API_KEY=your-key -e PDCP_TEAM_ID=your-id $ImageName" -ForegroundColor Gray
+ exit 1
+}
+
+Write-Host ""
+Write-Host "=== Deployment completed successfully ===" -ForegroundColor Green
+Write-Host ""
+Write-Host "Container Name: $ContainerName" -ForegroundColor Cyan
+Write-Host "Image: $ImageName" -ForegroundColor Cyan
+Write-Host "Container ID: $newContainerId" -ForegroundColor Cyan
+Write-Host ""
+Write-Host "Useful commands:" -ForegroundColor Yellow
+Write-Host " docker logs $ContainerName -f # View logs" -ForegroundColor Gray
+Write-Host " docker stop $ContainerName # Stop container" -ForegroundColor Gray
+Write-Host " docker restart $ContainerName # Restart container" -ForegroundColor Gray
+Write-Host " docker ps # List running containers" -ForegroundColor Gray
+
diff --git a/setup.go.ps1 b/setup.go.ps1
new file mode 100644
index 0000000..4faf335
--- /dev/null
+++ b/setup.go.ps1
@@ -0,0 +1,427 @@
+#Requires -RunAsAdministrator
+
+<#
+.SYNOPSIS
+ Automated silent installation and update of Go (Golang) for Windows
+
+.DESCRIPTION
+ This script downloads and installs Go (Golang) for Windows silently.
+ It checks if Go is already installed and updates it if a newer version is available.
+#>
+
+$ErrorActionPreference = "Stop"
+
+# Check if running as Administrator
+$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+if (-not $isAdmin) {
+ Write-Error "This script must be run as Administrator. Please run PowerShell as Administrator and try again."
+ exit 1
+}
+
+# Function to check if Go is already installed
+function Test-GoInstalled {
+ try {
+ $goVersion = go version 2>$null
+ if ($goVersion) {
+ return @{ Installed = $true; Version = $goVersion }
+ }
+ } catch {
+ # Go not found
+ }
+
+ # Check for Go installation directory
+ $goPaths = @(
+ "${env:ProgramFiles}\Go",
+ "${env:ProgramFiles(x86)}\Go",
+ "$env:LOCALAPPDATA\Go"
+ )
+
+ foreach ($path in $goPaths) {
+ if (Test-Path $path) {
+ $goExe = Join-Path $path "bin\go.exe"
+ if (Test-Path $goExe) {
+ return @{ Installed = $true; Version = "Unknown (installation found)" }
+ }
+ }
+ }
+
+ return @{ Installed = $false; Version = $null }
+}
+
+# Function to get installed Go version number
+function Get-InstalledGoVersion {
+ try {
+ $versionOutput = go version 2>$null
+ if ($versionOutput -match 'go(\d+\.\d+(?:\.\d+)?)') {
+ return $matches[1]
+ }
+ # Try alternative format
+ if ($versionOutput -match '(\d+\.\d+(?:\.\d+)?)') {
+ return $matches[1]
+ }
+ } catch {
+ # Version parsing failed
+ }
+
+ # Try to get version from Go installation
+ $goPaths = @(
+ "${env:ProgramFiles}\Go",
+ "${env:ProgramFiles(x86)}\Go",
+ "$env:LOCALAPPDATA\Go"
+ )
+
+ foreach ($path in $goPaths) {
+ $goExe = Join-Path $path "bin\go.exe"
+ if (Test-Path $goExe) {
+ try {
+ $fileVersion = (Get-Item $goExe).VersionInfo.FileVersion
+ if ($fileVersion) {
+ return $fileVersion
+ }
+ } catch {
+ # Version info not available
+ }
+ }
+ }
+
+ return $null
+}
+
+# Function to get latest Go version from golang.org
+function Get-LatestGoVersion {
+ try {
+ $ProgressPreference = 'SilentlyContinue'
+ # Use golang.org/dl API to get latest stable version
+ $releasesUrl = "https://go.dev/dl/?mode=json"
+ $response = Invoke-RestMethod -Uri $releasesUrl -UseBasicParsing -ErrorAction Stop
+
+ if ($response -and $response.Count -gt 0) {
+ # Filter for stable releases (not beta/rc) and Windows amd64
+ $stableReleases = $response | Where-Object {
+ $_.version -notmatch 'beta|rc' -and
+ ($_.files | Where-Object { $_.os -eq 'windows' -and $_.arch -eq 'amd64' })
+ } | Sort-Object -Property @{Expression={[Version]($_.version -replace '^go','')}} -Descending
+
+ if ($stableReleases -and $stableReleases.Count -gt 0) {
+ $latest = $stableReleases[0]
+ $version = $latest.version -replace '^go', ''
+ return $version
+ }
+ }
+ } catch {
+ Write-Warning "Could not fetch latest version from golang.org API: $_"
+ }
+
+ # Fallback: try to get from GitHub releases
+ try {
+ $ProgressPreference = 'SilentlyContinue'
+ $releasesUrl = "https://api.github.com/repos/golang/go/releases/latest"
+ $response = Invoke-RestMethod -Uri $releasesUrl -UseBasicParsing -ErrorAction Stop
+
+ if ($response.tag_name) {
+ # Remove 'go' prefix if present
+ $version = $response.tag_name -replace '^go', ''
+ return $version
+ }
+ } catch {
+ Write-Warning "Could not fetch latest version from GitHub API: $_"
+ }
+
+ # Fallback: return null to indicate we should proceed with installation/update
+ return $null
+}
+
+# Function to compare version numbers
+function Compare-Version {
+ param(
+ [string]$Version1,
+ [string]$Version2
+ )
+
+ if ([string]::IsNullOrEmpty($Version1) -or [string]::IsNullOrEmpty($Version2)) {
+ return $null
+ }
+
+ try {
+ $v1 = [Version]$Version1
+ $v2 = [Version]$Version2
+
+ if ($v1 -gt $v2) {
+ return 1
+ } elseif ($v1 -lt $v2) {
+ return -1
+ } else {
+ return 0
+ }
+ } catch {
+ Write-Warning "Version comparison failed: $_"
+ return $null
+ }
+}
+
+# Function to find Go installation directory
+function Get-GoInstallPath {
+ $goPaths = @(
+ "${env:ProgramFiles}\Go",
+ "${env:ProgramFiles(x86)}\Go",
+ "$env:LOCALAPPDATA\Go"
+ )
+
+ foreach ($path in $goPaths) {
+ if (Test-Path $path) {
+ $goExe = Join-Path $path "bin\go.exe"
+ if (Test-Path $goExe) {
+ return $path
+ }
+ }
+ }
+
+ return $null
+}
+
+# Function to uninstall Go
+function Uninstall-Go {
+ Write-Host "Uninstalling existing Go installation..." -ForegroundColor Cyan
+
+ $goPath = Get-GoInstallPath
+ if ($goPath) {
+ Write-Host "Found Go installation at: $goPath" -ForegroundColor Cyan
+
+ # Try to find uninstaller
+ $uninstallPaths = @(
+ (Join-Path $goPath "Uninstall.exe"),
+ (Join-Path (Split-Path $goPath) "Go\Uninstall.exe")
+ )
+
+ $uninstallerFound = $false
+ foreach ($uninstallPath in $uninstallPaths) {
+ if (Test-Path $uninstallPath) {
+ Write-Host "Found uninstaller at: $uninstallPath" -ForegroundColor Cyan
+ try {
+ $process = Start-Process -FilePath $uninstallPath -ArgumentList @("/S") -Wait -PassThru -NoNewWindow -ErrorAction Stop
+
+ if ($process.ExitCode -eq 0) {
+ Write-Host "Go uninstalled successfully." -ForegroundColor Green
+ $uninstallerFound = $true
+ Start-Sleep -Seconds 3
+ break
+ }
+ } catch {
+ Write-Warning "Error running uninstaller: $_"
+ }
+ }
+ }
+
+ if (-not $uninstallerFound) {
+ # Try to remove via MSI if it was installed via MSI
+ try {
+ $product = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -like "*Go*" } | Select-Object -First 1
+ if ($product) {
+ Write-Host "Uninstalling Go via MSI..." -ForegroundColor Cyan
+ $product.Uninstall() | Out-Null
+ Start-Sleep -Seconds 3
+ $uninstallerFound = $true
+ }
+ } catch {
+ Write-Warning "Could not uninstall via MSI: $_"
+ }
+ }
+
+ if (-not $uninstallerFound) {
+ Write-Warning "Could not find Go uninstaller. The new installation will overwrite the existing one."
+ }
+ } else {
+ Write-Warning "Could not find Go installation directory."
+ }
+}
+
+# Function to update PATH environment variable
+function Update-GoPath {
+ $goPath = Get-GoInstallPath
+ if (-not $goPath) {
+ return
+ }
+
+ $goBinPath = Join-Path $goPath "bin"
+ $currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine")
+
+ if ($currentPath -notlike "*$goBinPath*") {
+ Write-Host "Adding Go to system PATH..." -ForegroundColor Cyan
+ $newPath = $currentPath + ";$goBinPath"
+ [Environment]::SetEnvironmentVariable("Path", $newPath, "Machine")
+ $env:Path += ";$goBinPath"
+ Write-Host "Go bin directory added to PATH." -ForegroundColor Green
+ } else {
+ Write-Host "Go is already in PATH." -ForegroundColor Green
+ }
+}
+
+# Function to test if Go is accessible
+function Test-GoRunning {
+ try {
+ $null = go version 2>$null
+ return ($LASTEXITCODE -eq 0)
+ } catch {
+ return $false
+ }
+}
+
+# Check if Go is already installed
+$goStatus = Test-GoInstalled
+$needsUpdate = $false
+
+if ($goStatus.Installed) {
+ Write-Host "Go is already installed: $($goStatus.Version)" -ForegroundColor Green
+
+ # Check if update is needed
+ Write-Host "Checking for updates..." -ForegroundColor Cyan
+ $installedVersion = Get-InstalledGoVersion
+ $latestVersion = Get-LatestGoVersion
+
+ if ($installedVersion -and $latestVersion) {
+ $comparison = Compare-Version -Version1 $latestVersion -Version2 $installedVersion
+ if ($comparison -eq 1) {
+ Write-Host "Newer version available: $latestVersion (current: $installedVersion)" -ForegroundColor Yellow
+ $needsUpdate = $true
+ } elseif ($comparison -eq 0) {
+ Write-Host "Go is up to date (version $installedVersion)." -ForegroundColor Green
+ # Ensure PATH is set correctly
+ Update-GoPath
+ exit 0
+ } else {
+ Write-Host "Installed version ($installedVersion) is newer than latest available ($latestVersion)." -ForegroundColor Green
+ exit 0
+ }
+ } elseif (-not $latestVersion) {
+ Write-Host "Could not determine latest version. Skipping update check." -ForegroundColor Yellow
+ Update-GoPath
+ exit 0
+ } else {
+ Write-Host "Could not determine installed version. Proceeding with update..." -ForegroundColor Yellow
+ $needsUpdate = $true
+ }
+
+ if ($needsUpdate) {
+ Write-Host "`nUpdating Go..." -ForegroundColor Cyan
+ Uninstall-Go
+ }
+} else {
+ Write-Host "Go is not installed. Starting installation..." -ForegroundColor Cyan
+}
+
+# Create temporary directory for download
+$tempDir = Join-Path $env:TEMP "go-install"
+if (-not (Test-Path $tempDir)) {
+ New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
+}
+
+# Get latest version if not already determined
+if (-not $latestVersion) {
+ $latestVersion = Get-LatestGoVersion
+}
+
+if (-not $latestVersion) {
+ Write-Error "Could not determine latest Go version. Please check your internet connection and try again."
+ exit 1
+}
+
+# Go MSI installer download URL
+$goInstallerUrl = "https://go.dev/dl/go$latestVersion.windows-amd64.msi"
+$goInstallerPath = Join-Path $tempDir "go-installer.msi"
+
+try {
+ # Download Go installer
+ $action = if ($needsUpdate) { "update" } else { "installation" }
+ Write-Host "Downloading Go installer for $action..." -ForegroundColor Cyan
+ Write-Host "Version: $latestVersion" -ForegroundColor Gray
+ Write-Host "Source: $goInstallerUrl" -ForegroundColor Gray
+
+ try {
+ $ProgressPreference = 'SilentlyContinue'
+ Invoke-WebRequest -Uri $goInstallerUrl -OutFile $goInstallerPath -UseBasicParsing -ErrorAction Stop
+ } catch {
+ throw "Failed to download Go installer from $goInstallerUrl : $_"
+ }
+
+ if (-not (Test-Path $goInstallerPath)) {
+ throw "Downloaded file not found at expected location: $goInstallerPath"
+ }
+
+ $fileSize = (Get-Item $goInstallerPath).Length / 1MB
+ Write-Host "Downloaded installer size: $([math]::Round($fileSize, 2)) MB" -ForegroundColor Gray
+
+ Write-Host "Download completed. Starting silent $action..." -ForegroundColor Cyan
+ Write-Host "This may take a few minutes. Please wait..." -ForegroundColor Yellow
+
+ # Install Go silently using msiexec
+ # /i = Install
+ # /quiet = Silent installation
+ # /norestart = Don't restart
+ # /qn = No UI
+ $installArgs = @(
+ "/i",
+ "`"$goInstallerPath`"",
+ "/quiet",
+ "/norestart",
+ "/qn"
+ )
+
+ $process = Start-Process -FilePath "msiexec.exe" -ArgumentList $installArgs -Wait -PassThru -NoNewWindow
+
+ if ($process.ExitCode -eq 0 -or $process.ExitCode -eq 3010) {
+ # Exit code 0 = success, 3010 = success but requires reboot
+ if ($needsUpdate) {
+ Write-Host "Go update completed successfully!" -ForegroundColor Green
+ } else {
+ Write-Host "Go installation completed successfully!" -ForegroundColor Green
+ }
+
+ if ($process.ExitCode -eq 3010) {
+ Write-Host "A system reboot is required to complete the $action." -ForegroundColor Yellow
+ Write-Host "Please restart your computer and Go will be ready to use." -ForegroundColor Yellow
+ } else {
+ $actionPast = if ($needsUpdate) { "updated" } else { "installed" }
+ Write-Host "Go has been $actionPast." -ForegroundColor Green
+
+ # Update PATH
+ Update-GoPath
+
+ # Refresh environment variables in current session
+ $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
+
+ # Wait a moment for installation to complete
+ Write-Host "Waiting for installation to finalize..." -ForegroundColor Cyan
+ Start-Sleep -Seconds 5
+
+ # Verify Go is accessible
+ if (Test-GoRunning) {
+ $finalVersion = go version 2>$null
+ if ($finalVersion) {
+ Write-Host "Go is installed and accessible: $finalVersion" -ForegroundColor Green
+ }
+ } else {
+ Write-Host "Go has been installed, but you may need to:" -ForegroundColor Yellow
+ Write-Host " 1. Restart your PowerShell session" -ForegroundColor Yellow
+ Write-Host " 2. Or restart your computer" -ForegroundColor Yellow
+ Write-Host " 3. Verify PATH includes: $(Get-GoInstallPath)\bin" -ForegroundColor Yellow
+ }
+ }
+ } else {
+ throw "Go $action failed with exit code: $($process.ExitCode)"
+ }
+
+} catch {
+ Write-Error "Error during Go $action : $_"
+ exit 1
+} finally {
+ # Clean up installer file
+ if (Test-Path $goInstallerPath) {
+ Remove-Item -Path $goInstallerPath -Force -ErrorAction SilentlyContinue
+ }
+ if (Test-Path $tempDir) {
+ Remove-Item -Path $tempDir -Force -ErrorAction SilentlyContinue
+ }
+}
+
+Write-Host "`nScript completed successfully!" -ForegroundColor Green
+
diff --git a/setup.windows.desktop.ps1 b/setup.windows.desktop.ps1
new file mode 100644
index 0000000..9884cf1
--- /dev/null
+++ b/setup.windows.desktop.ps1
@@ -0,0 +1,356 @@
+#Requires -RunAsAdministrator
+
+<#
+.SYNOPSIS
+ Automated silent installation and update of Docker Desktop for Windows
+
+.DESCRIPTION
+ This script downloads and installs Docker Desktop for Windows silently.
+ It checks if Docker is already installed and updates it if a newer version is available.
+#>
+
+$ErrorActionPreference = "Stop"
+
+# Check if running as Administrator
+$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+if (-not $isAdmin) {
+ Write-Error "This script must be run as Administrator. Please run PowerShell as Administrator and try again."
+ exit 1
+}
+
+# Function to check if Docker is already installed
+function Test-DockerInstalled {
+ try {
+ $dockerVersion = docker --version 2>$null
+ if ($dockerVersion) {
+ return @{ Installed = $true; Version = $dockerVersion }
+ }
+ } catch {
+ # Docker not found
+ }
+
+ # Check for Docker Desktop service
+ $dockerService = Get-Service -Name "com.docker.service" -ErrorAction SilentlyContinue
+ if ($dockerService) {
+ return @{ Installed = $true; Version = "Unknown (service exists)" }
+ }
+
+ # Check for Docker Desktop executable
+ $dockerDesktopPath = "${env:ProgramFiles}\Docker\Docker\Docker Desktop.exe"
+ if (Test-Path $dockerDesktopPath) {
+ return @{ Installed = $true; Version = "Unknown (executable exists)" }
+ }
+
+ # Check for Docker service (for Docker CE installations)
+ $dockerServiceCE = Get-Service -Name "docker" -ErrorAction SilentlyContinue
+ if ($dockerServiceCE) {
+ return @{ Installed = $true; Version = "Docker CE (service exists)" }
+ }
+
+ return @{ Installed = $false; Version = $null }
+}
+
+# Function to wait for Docker daemon to be ready
+function Wait-Docker {
+ Write-Host "Waiting for Docker daemon to be ready..." -ForegroundColor Cyan
+ $dockerReady = $false
+ $startTime = Get-Date
+ $maxWaitMinutes = 2
+
+ while (-not $dockerReady) {
+ try {
+ $null = docker version 2>$null
+ if ($LASTEXITCODE -eq 0) {
+ $dockerReady = $true
+ Write-Host "Docker daemon is ready." -ForegroundColor Green
+ return $true
+ }
+ } catch {
+ # Docker not ready yet
+ }
+
+ $timeElapsed = $(Get-Date) - $startTime
+ if ($timeElapsed.TotalMinutes -ge $maxWaitMinutes) {
+ Write-Warning "Docker daemon did not become ready within $maxWaitMinutes minutes."
+ return $false
+ }
+
+ Start-Sleep -Seconds 2
+ }
+
+ return $false
+}
+
+# Function to test if Docker is running and accessible
+function Test-DockerRunning {
+ try {
+ $null = docker version 2>$null
+ return ($LASTEXITCODE -eq 0)
+ } catch {
+ return $false
+ }
+}
+
+# Function to get installed Docker version number
+function Get-InstalledDockerVersion {
+ try {
+ $versionOutput = docker --version 2>$null
+ if ($versionOutput -match 'version (\d+\.\d+\.\d+)') {
+ return $matches[1]
+ }
+ # Try alternative format
+ if ($versionOutput -match '(\d+\.\d+\.\d+)') {
+ return $matches[1]
+ }
+ } catch {
+ # Version parsing failed
+ }
+
+ # Try to get version from Docker Desktop installation
+ $dockerDesktopPath = "${env:ProgramFiles}\Docker\Docker\Docker Desktop.exe"
+ if (Test-Path $dockerDesktopPath) {
+ $fileVersion = (Get-Item $dockerDesktopPath).VersionInfo.FileVersion
+ if ($fileVersion) {
+ return $fileVersion
+ }
+ }
+
+ return $null
+}
+
+# Function to get latest Docker Desktop version from GitHub releases
+function Get-LatestDockerVersion {
+ try {
+ $ProgressPreference = 'SilentlyContinue'
+ $releasesUrl = "https://api.github.com/repos/docker/docker-desktop/releases/latest"
+ $response = Invoke-RestMethod -Uri $releasesUrl -UseBasicParsing -ErrorAction Stop
+
+ if ($response.tag_name) {
+ # Remove 'v' prefix if present
+ $version = $response.tag_name -replace '^v', ''
+ return $version
+ }
+ } catch {
+ Write-Warning "Could not fetch latest version from GitHub API: $_"
+ }
+
+ # Fallback: return null to indicate we should proceed with installation/update
+ return $null
+}
+
+# Function to compare version numbers
+function Compare-Version {
+ param(
+ [string]$Version1,
+ [string]$Version2
+ )
+
+ if ([string]::IsNullOrEmpty($Version1) -or [string]::IsNullOrEmpty($Version2)) {
+ return $null
+ }
+
+ try {
+ $v1 = [Version]$Version1
+ $v2 = [Version]$Version2
+
+ if ($v1 -gt $v2) {
+ return 1
+ } elseif ($v1 -lt $v2) {
+ return -1
+ } else {
+ return 0
+ }
+ } catch {
+ Write-Warning "Version comparison failed: $_"
+ return $null
+ }
+}
+
+# Function to uninstall Docker Desktop
+function Uninstall-DockerDesktop {
+ Write-Host "Uninstalling existing Docker Desktop..." -ForegroundColor Cyan
+
+ # Stop Docker Desktop service if running
+ try {
+ $dockerService = Get-Service -Name "com.docker.service" -ErrorAction SilentlyContinue
+ if ($dockerService -and $dockerService.Status -eq 'Running') {
+ Write-Host "Stopping Docker Desktop service..." -ForegroundColor Cyan
+ Stop-Service -Name "com.docker.service" -Force -ErrorAction SilentlyContinue
+ Start-Sleep -Seconds 3
+ }
+ } catch {
+ Write-Warning "Could not stop Docker Desktop service: $_"
+ }
+
+ # Try to find Docker Desktop uninstaller
+ $uninstallPaths = @(
+ "${env:ProgramFiles}\Docker\Docker\uninstall.exe",
+ "${env:ProgramFiles(x86)}\Docker\Docker\uninstall.exe",
+ "${env:LocalAppData}\Programs\Docker\Docker\uninstall.exe"
+ )
+
+ $uninstallerFound = $false
+ foreach ($path in $uninstallPaths) {
+ if (Test-Path $path) {
+ Write-Host "Found uninstaller at: $path" -ForegroundColor Cyan
+ try {
+ $process = Start-Process -FilePath $path -ArgumentList @("--quiet", "--unattended") -Wait -PassThru -NoNewWindow -ErrorAction Stop
+
+ if ($process.ExitCode -eq 0 -or $process.ExitCode -eq 3010) {
+ Write-Host "Docker Desktop uninstalled successfully." -ForegroundColor Green
+ $uninstallerFound = $true
+
+ # Wait a bit for cleanup
+ Write-Host "Waiting for cleanup to complete..." -ForegroundColor Cyan
+ Start-Sleep -Seconds 5
+ break
+ } else {
+ Write-Warning "Uninstaller exited with code: $($process.ExitCode)"
+ }
+ } catch {
+ Write-Warning "Error running uninstaller at $path : $_"
+ }
+ }
+ }
+
+ if (-not $uninstallerFound) {
+ Write-Warning "Could not find Docker Desktop uninstaller. Proceeding with installation anyway..."
+ Write-Warning "The new installation may overwrite the existing installation."
+ }
+}
+
+# Check if Docker is already installed
+$dockerStatus = Test-DockerInstalled
+$needsUpdate = $false
+
+if ($dockerStatus.Installed) {
+ Write-Host "Docker is already installed: $($dockerStatus.Version)" -ForegroundColor Green
+
+ # Check if update is needed
+ Write-Host "Checking for updates..." -ForegroundColor Cyan
+ $installedVersion = Get-InstalledDockerVersion
+ $latestVersion = Get-LatestDockerVersion
+
+ if ($installedVersion -and $latestVersion) {
+ $comparison = Compare-Version -Version1 $latestVersion -Version2 $installedVersion
+ if ($comparison -eq 1) {
+ Write-Host "Newer version available: $latestVersion (current: $installedVersion)" -ForegroundColor Yellow
+ $needsUpdate = $true
+ } elseif ($comparison -eq 0) {
+ Write-Host "Docker is up to date (version $installedVersion)." -ForegroundColor Green
+ exit 0
+ } else {
+ Write-Host "Installed version ($installedVersion) is newer than latest available ($latestVersion)." -ForegroundColor Green
+ exit 0
+ }
+ } elseif (-not $latestVersion) {
+ Write-Host "Could not determine latest version. Skipping update check." -ForegroundColor Yellow
+ exit 0
+ } else {
+ Write-Host "Could not determine installed version. Proceeding with update..." -ForegroundColor Yellow
+ $needsUpdate = $true
+ }
+
+ if ($needsUpdate) {
+ Write-Host "`nUpdating Docker Desktop..." -ForegroundColor Cyan
+ Uninstall-DockerDesktop
+ }
+} else {
+ Write-Host "Docker is not installed. Starting installation..." -ForegroundColor Cyan
+}
+
+# Create temporary directory for download
+$tempDir = Join-Path $env:TEMP "docker-install"
+if (-not (Test-Path $tempDir)) {
+ New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
+}
+
+# Docker Desktop download URL (latest stable version)
+$dockerInstallerUrl = "https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe"
+$dockerInstallerPath = Join-Path $tempDir "DockerDesktopInstaller.exe"
+
+try {
+ # Download Docker Desktop installer
+ $action = if ($needsUpdate) { "update" } else { "installation" }
+ Write-Host "Downloading Docker Desktop installer for $action..." -ForegroundColor Cyan
+ Write-Host "Source: $dockerInstallerUrl" -ForegroundColor Gray
+
+ try {
+ $ProgressPreference = 'SilentlyContinue'
+ Invoke-WebRequest -Uri $dockerInstallerUrl -OutFile $dockerInstallerPath -UseBasicParsing -ErrorAction Stop
+ } catch {
+ throw "Failed to download Docker Desktop installer from $dockerInstallerUrl : $_"
+ }
+
+ if (-not (Test-Path $dockerInstallerPath)) {
+ throw "Downloaded file not found at expected location: $dockerInstallerPath"
+ }
+
+ $fileSize = (Get-Item $dockerInstallerPath).Length / 1MB
+ Write-Host "Downloaded installer size: $([math]::Round($fileSize, 2)) MB" -ForegroundColor Gray
+
+ Write-Host "Download completed. Starting silent $action..." -ForegroundColor Cyan
+ Write-Host "This may take several minutes. Please wait..." -ForegroundColor Yellow
+
+ # Install Docker Desktop silently
+ # install = Install/update mode
+ # --quiet = Quiet mode (no UI)
+ # --accept-license = Accept license agreement
+ $installArgs = @(
+ "install",
+ "--quiet",
+ "--accept-license"
+ )
+
+ $process = Start-Process -FilePath $dockerInstallerPath -ArgumentList $installArgs -Wait -PassThru -NoNewWindow
+
+ if ($process.ExitCode -eq 0 -or $process.ExitCode -eq 3010) {
+ # Exit code 0 = success, 3010 = success but requires reboot
+ if ($needsUpdate) {
+ Write-Host "Docker Desktop update completed successfully!" -ForegroundColor Green
+ } else {
+ Write-Host "Docker Desktop installation completed successfully!" -ForegroundColor Green
+ }
+
+ if ($process.ExitCode -eq 3010) {
+ Write-Host "A system reboot is required to complete the $action." -ForegroundColor Yellow
+ Write-Host "Please restart your computer and Docker Desktop will be ready to use." -ForegroundColor Yellow
+ } else {
+ $actionPast = if ($needsUpdate) { "updated" } else { "installed" }
+ Write-Host "Docker Desktop has been $actionPast." -ForegroundColor Green
+
+ # Wait a moment for services to start
+ Write-Host "Waiting for Docker services to initialize..." -ForegroundColor Cyan
+ Start-Sleep -Seconds 10
+
+ # Verify Docker is accessible (if not requiring reboot)
+ if (Test-DockerRunning) {
+ $finalVersion = docker --version 2>$null
+ if ($finalVersion) {
+ Write-Host "Docker is running: $finalVersion" -ForegroundColor Green
+ }
+ } else {
+ Write-Host "Docker Desktop may need a few more moments to start, or you may need to restart your computer." -ForegroundColor Yellow
+ }
+
+ Write-Host "After restart (if needed), Docker Desktop should start automatically." -ForegroundColor Yellow
+ }
+ } else {
+ throw "Docker Desktop $action failed with exit code: $($process.ExitCode)"
+ }
+
+} catch {
+ Write-Error "Error during Docker Desktop $action : $_"
+ exit 1
+} finally {
+ # Clean up installer file
+ if (Test-Path $dockerInstallerPath) {
+ Remove-Item -Path $dockerInstallerPath -Force -ErrorAction SilentlyContinue
+ }
+ if (Test-Path $tempDir) {
+ Remove-Item -Path $tempDir -Force -ErrorAction SilentlyContinue
+ }
+}
+
+Write-Host "`nScript completed successfully!" -ForegroundColor Green
+
diff --git a/setup.windows.server.ps1 b/setup.windows.server.ps1
new file mode 100644
index 0000000..aec1783
--- /dev/null
+++ b/setup.windows.server.ps1
@@ -0,0 +1,561 @@
+#Requires -RunAsAdministrator
+
+<#
+.SYNOPSIS
+ Automated silent installation and update of Docker Engine from binaries for Windows Server
+
+.DESCRIPTION
+ This script downloads and installs Docker Engine from static binaries for Windows Server.
+ It follows the official Docker documentation for binary installation:
+ https://docs.docker.com/engine/install/binaries/
+
+ Note: This installs Docker for Windows containers only (not Linux containers).
+ For Windows 10/11, use setup.windows.desktop.ps1 instead.
+#>
+
+$ErrorActionPreference = "Stop"
+
+# Check if running as Administrator
+$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+if (-not $isAdmin) {
+ Write-Error "This script must be run as Administrator. Please run PowerShell as Administrator and try again."
+ exit 1
+}
+
+# Docker installation paths
+$dockerInstallPath = Join-Path $env:ProgramFiles "Docker"
+$dockerExePath = Join-Path $dockerInstallPath "docker.exe"
+$dockerdExePath = Join-Path $dockerInstallPath "dockerd.exe"
+
+# Global flag for restart requirement
+$script:RebootRequired = $false
+
+# Check if this is a resume after restart (optional marker file)
+$resumeMarker = Join-Path $env:TEMP "docker-setup-resume.txt"
+
+# Function to check if Windows Containers feature is installed
+function Test-ContainersFeature {
+ try {
+ if (Get-Command Get-WindowsFeature -ErrorAction SilentlyContinue) {
+ # Windows Server with Server Manager
+ $feature = Get-WindowsFeature -Name Containers -ErrorAction SilentlyContinue
+ if ($feature) {
+ return $feature.Installed
+ }
+ } else {
+ # Windows Server Core or newer versions
+ $feature = Get-WindowsOptionalFeature -Online -FeatureName Containers -ErrorAction SilentlyContinue
+ if ($feature) {
+ return ($feature.State -eq "Enabled")
+ }
+ }
+ } catch {
+ Write-Warning "Could not check Containers feature status: $_"
+ }
+ return $false
+}
+
+# Function to install Windows Containers feature
+function Install-ContainersFeature {
+ Write-Host "Checking Windows Containers feature..." -ForegroundColor Cyan
+
+ if (Test-ContainersFeature) {
+ Write-Host "Windows Containers feature is already installed." -ForegroundColor Green
+ return
+ }
+
+ Write-Host "Installing Windows Containers feature..." -ForegroundColor Cyan
+ Write-Host "This may take several minutes and may require a system restart." -ForegroundColor Yellow
+
+ try {
+ if (Get-Command Install-WindowsFeature -ErrorAction SilentlyContinue) {
+ # Windows Server with Server Manager
+ $result = Install-WindowsFeature -Name Containers -IncludeManagementTools
+
+ if ($result.RestartNeeded -eq "Yes") {
+ $script:RebootRequired = $true
+ Write-Host "A system restart is required to complete the Containers feature installation." -ForegroundColor Yellow
+ }
+
+ if ($result.Success) {
+ Write-Host "Windows Containers feature installed successfully." -ForegroundColor Green
+ } else {
+ throw "Failed to install Containers feature"
+ }
+ } else {
+ # Windows Server Core or newer versions
+ $result = Enable-WindowsOptionalFeature -Online -FeatureName Containers -All -NoRestart
+
+ if ($result.RestartNeeded -eq $true) {
+ $script:RebootRequired = $true
+ Write-Host "A system restart is required to complete the Containers feature installation." -ForegroundColor Yellow
+ }
+
+ Write-Host "Windows Containers feature installed successfully." -ForegroundColor Green
+ }
+ } catch {
+ Write-Error "Failed to install Containers feature: $_"
+ throw
+ }
+}
+
+# Function to check if restart is required and handle it
+function Test-AndHandleRestart {
+ param(
+ [switch]$Silent
+ )
+
+ if ($script:RebootRequired) {
+ Write-Host ""
+ Write-Host "=== RESTART REQUIRED ===" -ForegroundColor Yellow
+ Write-Host "A system restart is required to complete the installation." -ForegroundColor Yellow
+ Write-Host "Please restart your computer and then run this script again to continue." -ForegroundColor Yellow
+ Write-Host ""
+
+ # Create resume marker
+ "Resume after restart" | Out-File -FilePath $resumeMarker -Force
+
+ if (-not $Silent) {
+ $restart = Read-Host "Would you like to restart now? (Y/N)"
+ if ($restart -eq "Y" -or $restart -eq "y") {
+ Write-Host "Restarting computer in 10 seconds..." -ForegroundColor Cyan
+ Write-Host "Press Ctrl+C to cancel" -ForegroundColor Gray
+ Start-Sleep -Seconds 10
+ Restart-Computer -Force
+ } else {
+ Write-Host "Please restart your computer manually and run this script again." -ForegroundColor Yellow
+ Write-Host "The script will automatically resume after restart." -ForegroundColor Gray
+ exit 0
+ }
+ } else {
+ Write-Host "Please restart your computer manually and run this script again." -ForegroundColor Yellow
+ Write-Host "The script will automatically resume after restart." -ForegroundColor Gray
+ exit 0
+ }
+ }
+}
+
+# Function to check if Docker is already installed
+function Test-DockerInstalled {
+ try {
+ $dockerVersion = docker --version 2>$null
+ if ($dockerVersion) {
+ return @{ Installed = $true; Version = $dockerVersion }
+ }
+ } catch {
+ # Docker not found
+ }
+
+ # Check for Docker service
+ $dockerService = Get-Service -Name "docker" -ErrorAction SilentlyContinue
+ if ($dockerService) {
+ return @{ Installed = $true; Version = "Unknown (service exists)" }
+ }
+
+ # Check for Docker binaries
+ if (Test-Path $dockerExePath) {
+ return @{ Installed = $true; Version = "Unknown (binary exists)" }
+ }
+
+ return @{ Installed = $false; Version = $null }
+}
+
+# Function to wait for Docker daemon to be ready
+function Wait-Docker {
+ Write-Host "Waiting for Docker daemon to be ready..." -ForegroundColor Cyan
+ $dockerReady = $false
+ $startTime = Get-Date
+ $maxWaitMinutes = 2
+
+ while (-not $dockerReady) {
+ try {
+ $null = docker version 2>$null
+ if ($LASTEXITCODE -eq 0) {
+ $dockerReady = $true
+ Write-Host "Docker daemon is ready." -ForegroundColor Green
+ return $true
+ }
+ } catch {
+ # Docker not ready yet
+ }
+
+ $timeElapsed = $(Get-Date) - $startTime
+ if ($timeElapsed.TotalMinutes -ge $maxWaitMinutes) {
+ Write-Warning "Docker daemon did not become ready within $maxWaitMinutes minutes."
+ return $false
+ }
+
+ Start-Sleep -Seconds 2
+ }
+
+ return $false
+}
+
+# Function to test if Docker is running and accessible
+function Test-DockerRunning {
+ try {
+ $null = docker version 2>$null
+ return ($LASTEXITCODE -eq 0)
+ } catch {
+ return $false
+ }
+}
+
+# Function to get installed Docker version number
+function Get-InstalledDockerVersion {
+ try {
+ $versionOutput = docker --version 2>$null
+ if ($versionOutput -match 'version (\d+\.\d+\.\d+)') {
+ return $matches[1]
+ }
+ # Try alternative format
+ if ($versionOutput -match '(\d+\.\d+\.\d+)') {
+ return $matches[1]
+ }
+ } catch {
+ # Version parsing failed
+ }
+
+ return $null
+}
+
+# Function to get latest Docker version from download page
+function Get-LatestDockerVersion {
+ try {
+ $ProgressPreference = 'SilentlyContinue'
+ $downloadUrl = "https://download.docker.com/win/static/stable/x86_64/"
+ $response = Invoke-WebRequest -Uri $downloadUrl -UseBasicParsing -ErrorAction Stop
+
+ # Parse HTML to find latest version
+ # Look for links like "docker-24.0.0.zip"
+ $matches = [regex]::Matches($response.Content, 'docker-(\d+\.\d+\.\d+)\.zip')
+ if ($matches.Count -gt 0) {
+ $versions = $matches | ForEach-Object { [Version]$_.Groups[1].Value } | Sort-Object -Descending
+ if ($versions.Count -gt 0) {
+ return $versions[0].ToString()
+ }
+ }
+ } catch {
+ Write-Warning "Could not fetch latest version from download page: $_"
+ }
+
+ # Fallback: return null to indicate we should proceed with installation/update
+ return $null
+}
+
+# Function to compare version numbers
+function Compare-Version {
+ param(
+ [string]$Version1,
+ [string]$Version2
+ )
+
+ if ([string]::IsNullOrEmpty($Version1) -or [string]::IsNullOrEmpty($Version2)) {
+ return $null
+ }
+
+ try {
+ $v1 = [Version]$Version1
+ $v2 = [Version]$Version2
+
+ if ($v1 -gt $v2) {
+ return 1
+ } elseif ($v1 -lt $v2) {
+ return -1
+ } else {
+ return 0
+ }
+ } catch {
+ Write-Warning "Version comparison failed: $_"
+ return $null
+ }
+}
+
+# Function to stop and unregister Docker service
+function Stop-DockerService {
+ Write-Host "Stopping Docker service..." -ForegroundColor Cyan
+
+ try {
+ $dockerService = Get-Service -Name "docker" -ErrorAction SilentlyContinue
+ if ($dockerService -and $dockerService.Status -eq 'Running') {
+ Stop-Service -Name "docker" -Force -ErrorAction Stop
+ Write-Host "Docker service stopped." -ForegroundColor Green
+ Start-Sleep -Seconds 2
+ }
+ } catch {
+ Write-Warning "Could not stop Docker service: $_"
+ }
+
+ # Unregister service if it exists
+ try {
+ $service = Get-Service -Name "docker" -ErrorAction SilentlyContinue
+ if ($service) {
+ Write-Host "Unregistering Docker service..." -ForegroundColor Cyan
+ & $dockerdExePath --unregister-service 2>$null
+ Start-Sleep -Seconds 2
+ }
+ } catch {
+ Write-Warning "Could not unregister Docker service: $_"
+ }
+}
+
+# Function to update PATH environment variable
+function Update-DockerPath {
+ if (-not (Test-Path $dockerInstallPath)) {
+ Write-Warning "Docker installation path not found: $dockerInstallPath"
+ return
+ }
+
+ $currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine")
+
+ if ($currentPath -notlike "*$dockerInstallPath*") {
+ Write-Host "Adding Docker to system PATH..." -ForegroundColor Cyan
+ $newPath = $currentPath + ";$dockerInstallPath"
+ [Environment]::SetEnvironmentVariable("Path", $newPath, "Machine")
+
+ # Also update current session PATH
+ $env:Path += ";$dockerInstallPath"
+
+ Write-Host "Docker directory added to PATH: $dockerInstallPath" -ForegroundColor Green
+ } else {
+ Write-Host "Docker is already in system PATH." -ForegroundColor Green
+ }
+}
+
+# Check if resuming after restart
+if (Test-Path $resumeMarker) {
+ Write-Host "Resuming installation after restart..." -ForegroundColor Cyan
+ Remove-Item -Path $resumeMarker -Force -ErrorAction SilentlyContinue
+ Write-Host ""
+}
+
+# Install Windows Containers feature
+Write-Host "=== Installing Windows Containers Feature ===" -ForegroundColor Cyan
+Write-Host ""
+try {
+ Install-ContainersFeature
+ Test-AndHandleRestart -Silent
+} catch {
+ Write-Error "Failed to install Windows Containers feature: $_"
+ exit 1
+}
+Write-Host ""
+
+# Check if Docker is already installed
+Write-Host "=== Checking Docker Installation ===" -ForegroundColor Cyan
+Write-Host ""
+
+$dockerStatus = Test-DockerInstalled
+$needsUpdate = $false
+
+if ($dockerStatus.Installed) {
+ Write-Host "Docker is already installed: $($dockerStatus.Version)" -ForegroundColor Green
+
+ # Check if update is needed
+ Write-Host "Checking for updates..." -ForegroundColor Cyan
+ $installedVersion = Get-InstalledDockerVersion
+ $latestVersion = Get-LatestDockerVersion
+
+ if ($installedVersion -and $latestVersion) {
+ $comparison = Compare-Version -Version1 $latestVersion -Version2 $installedVersion
+ if ($comparison -eq 1) {
+ Write-Host "Newer version available: $latestVersion (current: $installedVersion)" -ForegroundColor Yellow
+ $needsUpdate = $true
+ } elseif ($comparison -eq 0) {
+ Write-Host "Docker is up to date (version $installedVersion)." -ForegroundColor Green
+ exit 0
+ } else {
+ Write-Host "Installed version ($installedVersion) is newer than latest available ($latestVersion)." -ForegroundColor Green
+ exit 0
+ }
+ } elseif (-not $latestVersion) {
+ Write-Host "Could not determine latest version. Skipping update check." -ForegroundColor Yellow
+ exit 0
+ } else {
+ Write-Host "Could not determine installed version. Proceeding with update..." -ForegroundColor Yellow
+ $needsUpdate = $true
+ }
+
+ if ($needsUpdate) {
+ Write-Host "`nUpdating Docker..." -ForegroundColor Cyan
+ Stop-DockerService
+ }
+} else {
+ Write-Host "Docker is not installed. Starting installation..." -ForegroundColor Cyan
+}
+
+Write-Host ""
+
+# Download and install Docker binaries
+Write-Host "=== Installing Docker Binaries ===" -ForegroundColor Cyan
+Write-Host ""
+
+# Get latest version if not already determined
+if (-not $latestVersion) {
+ $latestVersion = Get-LatestDockerVersion
+}
+
+if (-not $latestVersion) {
+ Write-Error "Could not determine latest Docker version. Please check your internet connection and try again."
+ exit 1
+}
+
+# Create temporary directory for download
+$tempDir = Join-Path $env:TEMP "docker-install"
+if (-not (Test-Path $tempDir)) {
+ New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
+}
+
+# Docker binary download URL
+$dockerZipUrl = "https://download.docker.com/win/static/stable/x86_64/docker-$latestVersion.zip"
+$dockerZipPath = Join-Path $tempDir "docker-$latestVersion.zip"
+
+try {
+ # Download Docker binaries
+ $action = if ($needsUpdate) { "update" } else { "installation" }
+ Write-Host "Downloading Docker binaries for $action..." -ForegroundColor Cyan
+ Write-Host "Version: $latestVersion" -ForegroundColor Gray
+ Write-Host "Source: $dockerZipUrl" -ForegroundColor Gray
+
+ try {
+ $ProgressPreference = 'SilentlyContinue'
+ Invoke-WebRequest -Uri $dockerZipUrl -OutFile $dockerZipPath -UseBasicParsing -ErrorAction Stop
+ } catch {
+ throw "Failed to download Docker binaries from $dockerZipUrl : $_"
+ }
+
+ if (-not (Test-Path $dockerZipPath)) {
+ throw "Downloaded file not found at expected location: $dockerZipPath"
+ }
+
+ $fileSize = (Get-Item $dockerZipPath).Length / 1MB
+ Write-Host "Downloaded archive size: $([math]::Round($fileSize, 2)) MB" -ForegroundColor Gray
+
+ Write-Host "Download completed. Extracting binaries..." -ForegroundColor Cyan
+
+ # Extract archive to Program Files
+ $extractPath = Join-Path $tempDir "docker-extract"
+ if (Test-Path $extractPath) {
+ Remove-Item -Path $extractPath -Recurse -Force -ErrorAction SilentlyContinue
+ }
+ New-Item -ItemType Directory -Path $extractPath -Force | Out-Null
+
+ Expand-Archive -Path $dockerZipPath -DestinationPath $extractPath -Force -ErrorAction Stop
+
+ # Find the docker directory in the extracted files
+ $dockerExtractedPath = Get-ChildItem -Path $extractPath -Directory | Where-Object { $_.Name -like "docker*" } | Select-Object -First 1
+ if (-not $dockerExtractedPath) {
+ # Sometimes files are extracted directly without a subdirectory
+ $dockerExtractedPath = $extractPath
+ }
+
+ Write-Host "Installing Docker binaries to $dockerInstallPath..." -ForegroundColor Cyan
+
+ # Create installation directory if it doesn't exist
+ if (-not (Test-Path $dockerInstallPath)) {
+ New-Item -ItemType Directory -Path $dockerInstallPath -Force | Out-Null
+ }
+
+ # Copy binaries
+ $binaries = @("docker.exe", "dockerd.exe")
+ foreach ($binary in $binaries) {
+ $sourcePath = Join-Path $dockerExtractedPath.FullName $binary
+ if (Test-Path $sourcePath) {
+ Copy-Item -Path $sourcePath -Destination (Join-Path $dockerInstallPath $binary) -Force -ErrorAction Stop
+ Write-Host "Installed $binary" -ForegroundColor Green
+ } else {
+ Write-Warning "Binary $binary not found in archive"
+ }
+ }
+
+ # Update PATH environment variable
+ Update-DockerPath
+
+ # Refresh PATH in current session
+ $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
+
+ # Register Docker service
+ Write-Host "Registering Docker service..." -ForegroundColor Cyan
+ $registerResult = & $dockerdExePath --register-service 2>&1
+ if ($LASTEXITCODE -ne 0) {
+ Write-Warning "Service registration output: $registerResult"
+ # Continue anyway, service might already be registered
+ } else {
+ Write-Host "Docker service registered successfully." -ForegroundColor Green
+ }
+
+ # Start Docker service
+ Write-Host "Starting Docker service..." -ForegroundColor Cyan
+ try {
+ Start-Service -Name "docker" -ErrorAction Stop
+ Write-Host "Docker service started successfully." -ForegroundColor Green
+ } catch {
+ Write-Warning "Could not start Docker service: $_"
+ Write-Host "You may need to start it manually: Start-Service docker" -ForegroundColor Yellow
+ }
+
+ # Wait a moment for service to start
+ Start-Sleep -Seconds 5
+
+ # Verify Docker is accessible
+ if (Test-DockerRunning) {
+ $finalVersion = docker --version 2>$null
+ if ($finalVersion) {
+ Write-Host "Docker is running: $finalVersion" -ForegroundColor Green
+ }
+
+ # Test with hello-world (Windows container)
+ Write-Host "Testing Docker installation..." -ForegroundColor Cyan
+ try {
+ docker run --rm hello-world:nanoserver 2>&1 | Out-Null
+ if ($LASTEXITCODE -eq 0) {
+ Write-Host "Docker installation verified successfully!" -ForegroundColor Green
+ }
+ } catch {
+ Write-Warning "Could not run test container, but Docker appears to be installed."
+ }
+ } else {
+ Write-Host "Docker service may need a few more moments to start." -ForegroundColor Yellow
+ Write-Host "You can verify with: docker version" -ForegroundColor Yellow
+ }
+
+ if ($needsUpdate) {
+ Write-Host "Docker update completed successfully!" -ForegroundColor Green
+ } else {
+ Write-Host "Docker installation completed successfully!" -ForegroundColor Green
+ }
+
+ # Check if restart is still required after Docker installation
+ Test-AndHandleRestart -Silent
+
+ # Remove resume marker if installation completed successfully
+ if (Test-Path $resumeMarker) {
+ Remove-Item -Path $resumeMarker -Force -ErrorAction SilentlyContinue
+ }
+
+} catch {
+ Write-Error "Error during Docker $action : $_"
+ exit 1
+} finally {
+ # Clean up temporary files
+ if (Test-Path $tempDir) {
+ Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
+ }
+}
+
+Write-Host "`nScript completed successfully!" -ForegroundColor Green
+Write-Host ""
+Write-Host "Docker binaries installed to: $dockerInstallPath" -ForegroundColor Cyan
+Write-Host "Docker has been added to system PATH." -ForegroundColor Green
+Write-Host ""
+Write-Host "Note: If you're running this script in a new PowerShell session, you may need to:" -ForegroundColor Yellow
+Write-Host " - Close and reopen your PowerShell window, OR" -ForegroundColor Gray
+Write-Host " - Run: `$env:Path = [System.Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path','User')" -ForegroundColor Gray
+Write-Host ""
+Write-Host "Useful commands:" -ForegroundColor Yellow
+Write-Host " docker version # Check Docker version" -ForegroundColor Gray
+Write-Host " docker run hello-world:nanoserver # Test with Windows container" -ForegroundColor Gray
+Write-Host " Get-Service docker # Check service status" -ForegroundColor Gray
+Write-Host " Start-Service docker # Start Docker service" -ForegroundColor Gray
+Write-Host " Stop-Service docker # Stop Docker service" -ForegroundColor Gray
+