scripts/modules/prerequisites/azure-cli/install-azure-cli.ps1
# strangeloop Setup - Azure CLI Installation Module # Version: 1.0.0 param( [string]$Version = "latest", [switch]${test-only}, [switch]${what-if} ) # Import shared modules $SharedPath = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent | Join-Path -ChildPath "shared" Import-Module "$SharedPath\write-functions.ps1" -Force -DisableNameChecking Import-Module "$SharedPath\test-functions.ps1" -Force -DisableNameChecking Import-Module "$SharedPath\path-functions.ps1" -Force -DisableNameChecking function Install-AzureCLIViaMSI { param( ) try { Write-Progress "Downloading Azure CLI installer..." # Download Azure CLI installer $azCliUrl = "https://aka.ms/installazurecliwindows" $tempDir = Join-Path $env:TEMP "AzureCLI-$(Get-Random)" New-Item -ItemType Directory -Path $tempDir -Force | Out-Null if (-not (Test-Path $tempDir)) { Write-Error "Could not create temporary directory" return $false } $installerPath = Join-Path $tempDir "AzureCLI.msi" try { Invoke-WebRequest -Uri $azCliUrl -OutFile $installerPath -UseBasicParsing Write-Success "Azure CLI installer downloaded successfully" } catch { Write-Error "Failed to download Azure CLI installer: $($_.Exception.Message)" Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue return $false } # Verify download if (-not (Test-Path $installerPath)) { Write-Error "Azure CLI installer not found after download" Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue return $false } Write-Progress "Installing Azure CLI via MSI..." # Install Azure CLI with elevation (required for MSI) $installResult = Invoke-CommandWithTimeout -ScriptBlock { Start-Process msiexec.exe -ArgumentList "/i", $using:installerPath, "/quiet", "/norestart" -Wait -NoNewWindow -PassThru -Verb RunAs } -TimeoutSeconds 600 -Description "Azure CLI MSI installation" # Clean up installer Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue if (-not $installResult.Success) { Write-Warning "Azure CLI MSI installation failed: $($installResult.Error)" return $false } $process = $installResult.Output if ($process.ExitCode -eq 0) { Write-Success "Azure CLI installed successfully via MSI" # Refresh PATH immediately after MSI installation Write-Info "Refreshing environment PATH after MSI installation..." $env:PATH = [Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [Environment]::GetEnvironmentVariable("PATH", "User") # Also try to refresh from registry try { $machinePath = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey("SYSTEM\CurrentControlSet\Control\Session Manager\Environment").GetValue("PATH", "", [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) $userPath = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey("Environment").GetValue("PATH", "", [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) $env:PATH = $machinePath + ";" + $userPath Write-Info "PATH refreshed from registry after MSI installation" } catch { Write-Warning "Could not refresh PATH from registry: $($_.Exception.Message)" } # Wait for PATH changes to take effect Start-Sleep -Seconds 3 return $true } else { Write-Warning "Azure CLI MSI installation failed with exit code: $($process.ExitCode)" return $false } } catch { Write-Warning "Azure CLI MSI installation failed: $($_.Exception.Message)" return $false } } function Test-AzureCLI { param( ) try { Write-Info "Testing Azure CLI installation..." # Check if Azure CLI command is available if (-not (Test-Command "az")) { Write-Warning "Azure CLI command 'az' not found" return $false } # Check Azure CLI version $azVersion = Get-ToolVersion "az" if (-not $azVersion) { Write-Warning "Could not get Azure CLI version" return $false } # Test basic functionality $versionOutput = az version --output json 2>$null if (-not $versionOutput) { Write-Warning "Azure CLI functionality test failed" return $false } Write-Success "Azure CLI is properly installed: $azVersion" return $true } catch { Write-Warning "Error testing Azure CLI: $($_.Exception.Message)" return $false } } function Install-AzureCLI { param( [string]$Version = "latest", [switch]${test-only}, [switch]${what-if} ) # If test-only mode, just test current installation if (${test-only}) { return Test-AzureCLI } # If what-if mode, show what would be done if (${what-if}) { Write-Host "what if: Would test if Azure CLI is already installed" -ForegroundColor Yellow Write-Host "what if: Would check internet connection" -ForegroundColor Yellow Write-Host "what if: Would attempt installation via winget (preferred method)" -ForegroundColor Yellow Write-Host "what if: Would fallback to MSI installer if winget fails" -ForegroundColor Yellow Write-Host "what if: Would verify installation and configure Azure CLI" -ForegroundColor Yellow return $true } Write-Step "Installing Azure CLI..." try { # Check if Azure CLI is already installed if (Test-Command "az") { $currentVersion = Get-ToolVersion "az" if ($currentVersion) { Write-Info "Azure CLI $currentVersion is already installed" Write-Success "Azure CLI installation confirmed" return $true } } # Check internet connection if (-not (Test-InternetConnection)) { Write-Error "Internet connection required for Azure CLI installation" return $false } Write-Progress "Attempting Azure CLI installation..." # Try installation methods in order of preference $installationSuccessful = $false # Method 1: Try winget (preferred - doesn't require admin) Write-Info "Attempting installation via winget (preferred method - no admin required)..." if (Test-Command "winget") { try { Write-Progress "Installing Azure CLI via winget..." Write-Info "Running: winget install Microsoft.AzureCLI --silent --accept-package-agreements --accept-source-agreements" # Use Invoke-CommandWithTimeout for better control $wingetResult = Invoke-CommandWithTimeout -ScriptBlock { $output = winget install "Microsoft.AzureCLI" --silent --accept-package-agreements --accept-source-agreements 2>&1 return @{ ExitCode = $LASTEXITCODE Output = $output } } -TimeoutSeconds 600 -Description "Azure CLI winget installation" if ($wingetResult.Success) { $result = $wingetResult.Output if ($result.ExitCode -eq 0) { Write-Success "Azure CLI installed successfully via winget" # Refresh PATH immediately after winget installation Write-Info "Refreshing environment PATH after installation..." $env:PATH = [Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [Environment]::GetEnvironmentVariable("PATH", "User") # Also try to refresh from registry for completeness try { $machinePath = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey("SYSTEM\CurrentControlSet\Control\Session Manager\Environment").GetValue("PATH", "", [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) $userPath = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey("Environment").GetValue("PATH", "", [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) $env:PATH = $machinePath + ";" + $userPath Write-Info "PATH refreshed from registry" } catch { Write-Warning "Could not refresh PATH from registry: $($_.Exception.Message)" } # Wait for PATH changes to take effect Start-Sleep -Seconds 3 $installationSuccessful = $true } elseif ($result.ExitCode -eq -1978335189) { # This exit code often means "already installed" in winget Write-Info "Azure CLI may already be installed (winget exit code: $($result.ExitCode))" Write-Info "Proceeding to verification..." # Still refresh PATH in case it's installed but not in current session Write-Info "Refreshing environment PATH..." $env:PATH = [Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [Environment]::GetEnvironmentVariable("PATH", "User") Start-Sleep -Seconds 2 $installationSuccessful = $true } else { Write-Warning "winget installation failed with exit code: $($result.ExitCode)" Write-Info "winget output: $($result.Output -join '; ')" } } else { Write-Warning "winget installation timed out or failed: $($wingetResult.Error)" } } catch { Write-Warning "winget installation failed with exception: $($_.Exception.Message)" } } else { Write-Warning "winget not available - this is the preferred installation method" Write-Info "Consider installing winget from Microsoft Store or GitHub releases" } # Method 2: Try elevated MSI installation if (-not $installationSuccessful) { Write-Info "Attempting elevated MSI installation..." if (-not (Test-AdminPrivileges)) { Write-Info "MSI installation requires administrator privileges - will prompt for elevation" } $installationSuccessful = Install-AzureCLIViaMSI } # Method 3: Provide manual installation guidance if (-not $installationSuccessful) { Write-Warning "Automated Azure CLI installation failed" Write-Host "" Write-Host "📋 Recommended Installation Methods (in order):" -ForegroundColor Cyan Write-Host "1. Install winget first, then retry: winget install Microsoft.AzureCLI" -ForegroundColor Green Write-Host "2. Run this script as Administrator for MSI installation" -ForegroundColor Yellow Write-Host "3. Download MSI from: https://aka.ms/installazurecliwindows" -ForegroundColor Yellow Write-Host "4. Install via web installer: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-windows" -ForegroundColor Yellow Write-Host "" Write-Host "💡 winget is the preferred method as it doesn't require admin privileges" -ForegroundColor Cyan Write-Host "After installation, restart your terminal and run this setup script again." -ForegroundColor Green return $false } # Update PATH and verify installation Write-Progress "Verifying Azure CLI installation..." # Refresh environment variables one more time for verification Write-Info "Final PATH refresh for verification..." $env:PATH = [Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [Environment]::GetEnvironmentVariable("PATH", "User") # Give more time for PATH changes to take effect after installation Write-Info "Waiting for PATH changes to take effect..." Start-Sleep -Seconds 5 # Test if az command is now available Write-Info "Testing if 'az' command is available..." $azCommand = Get-Command "az" -ErrorAction SilentlyContinue if ($azCommand) { Write-Info "Found az command at: $($azCommand.Source)" } else { Write-Warning "az command not found in PATH after installation" Write-Info "Current PATH directories containing 'Azure' or 'CLI':" $env:PATH -split ';' | Where-Object { $_ -like '*Azure*' -or $_ -like '*CLI*' } | ForEach-Object { Write-Info " - $_" } } # Verify installation if (Test-Command "az") { $installedVersion = Get-ToolVersion "az" if ($installedVersion) { Write-Success "Azure CLI $installedVersion verified and ready" return $true } else { Write-Warning "Azure CLI installed but version detection failed" return $true # Still consider it successful if command exists } } else { Write-Error "Azure CLI installation completed but 'az' command not found" Write-Info "You may need to restart your terminal or reboot your system" Write-Info "Try running: refreshenv (if using Chocolatey) or restart your terminal" return $false } } catch { Write-Error "Azure CLI installation failed: $($_.Exception.Message)" return $false } } # Main execution if ($MyInvocation.InvocationName -ne '.') { $result = Install-AzureCLI -Version $Version -test-only:${test-only} -what-if:${what-if} if ($result) { if (${test-only}) { Write-Success "Azure CLI test completed successfully" } else { Write-CompletionSummary @{ 'Azure CLI Installation' = 'Completed Successfully' 'Version' = (Get-ToolVersion "az") 'Command Available' = if (Test-Command "az") { "Yes" } else { "No" } } -Title "Azure CLI Installation Summary" } } else { if (${test-only}) { Write-Error "Azure CLI test failed" } else { Write-Error "Azure CLI installation failed" } } # Return the result for the calling script return $result } # Export functions for module usage Export-ModuleMember -Function @( 'Install-AzureCLI', 'Install-AzureCLIViaMSI' ) |