Last active
September 4, 2025 01:06
-
-
Save emilwojcik93/7eb1e172f8bb038e324c6e4a7f4ccaaa to your computer and use it in GitHub Desktop.
The script searches for certificates with a specific description pattern. It exports each certificate, installs them in WSL, and checks the response using curl. If the response is not correct, it tries the next certificate from the results. If the list is done and WSL still responds with an incorrect answer, it throws an error.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Requires -Version 5.1 | |
<# | |
.SYNOPSIS | |
Universal Corporate Certificate Installer for WSL and Development Environments | |
.DESCRIPTION | |
A comprehensive, generic script for automatically installing corporate certificates | |
in WSL distributions and configuring development environments. Designed to work | |
across different corporate environments without containing sensitive data. | |
This script: | |
- Detects corporate certificates using configurable patterns | |
- Automatically detects WSL distributions from Windows registry | |
- Installs certificates in WSL using proper path handling | |
- Optionally extracts intermediate certificates from live connections | |
- Configures Node.js and other development environment variables | |
- Tests connectivity with popular domains | |
- Creates optimized certificate bundles | |
Can be executed remotely via: | |
&([ScriptBlock]::Create((irm https://gist.githubusercontent.com/emilwojcik93/7eb1e172f8bb038e324c6e4a7f4ccaaa/raw/Auto-Install-CertificatesInWSL.ps1))) -Verbose | |
.PARAMETER DescriptionPatterns | |
Array of patterns to search for in certificate subjects/descriptions. | |
Default: @("CA", "Zscaler") | |
Example: @("Acme Corp", "Zscaler", "Corporate CA") | |
.PARAMETER ExcludeIssuers | |
Array of issuer names to exclude from certificate search results. | |
Default includes common public CAs that should not be exported. | |
.PARAMETER WSLDistro | |
Specific WSL distribution name to target. If not specified, uses registry-detected default distro. | |
Example: "Ubuntu-20.04", "Debian", "Ubuntu" | |
.PARAMETER SetupNodeJS | |
Configure Windows Node.js environment variables for certificate bundle usage. | |
This is a switch parameter - include it to enable Windows Node.js configuration. | |
.PARAMETER ExtractIntermediate | |
Attempt to extract intermediate certificates from live HTTPS connections. | |
Default: $true | |
.PARAMETER TestUrls | |
Array of URLs to test certificate installation against. | |
Default: @("https://google.com", "https://github.com", "https://www.microsoft.com", "https://api.github.com") | |
.PARAMETER CertificateDirectory | |
Directory to export certificates to. Default: "$env:USERPROFILE\certificates" | |
.PARAMETER CreateBundle | |
Create a combined certificate bundle file for Node.js and other applications. | |
Default: $true | |
.PARAMETER SetupEnvironmentVars | |
Configure additional Windows environment variables (SSL_CERT_FILE, HTTPS_CA_BUNDLE, etc.). | |
This is a switch parameter - include it to enable additional Windows environment variables. | |
.PARAMETER SkipWSLInstall | |
Skip WSL certificate installation (export certificates only). | |
Default: $false | |
.PARAMETER ShowWSLInfo | |
Display detailed information about installed WSL distributions and exit. | |
This is a switch parameter - include it to show WSL information only. | |
.PARAMETER InstallAllDistros | |
Install certificates for ALL installed WSL distributions automatically. | |
This is a switch parameter - include it to target all distributions. | |
.PARAMETER OverwriteExisting | |
Overwrite existing certificate files in the output directory. | |
This is a switch parameter - include it to overwrite existing files. | |
.PARAMETER SetupWSLEnvironment | |
Configure comprehensive environment variables inside WSL distributions for development tools. | |
This is a switch parameter - include it to enable WSL environment variable setup. | |
.PARAMETER TestBeforeAndAfter | |
Test HTTPS connectivity before and after certificate installation with improvement metrics. | |
Default: $true | |
.EXAMPLE | |
.\Auto-Install-CertificatesInWSL.ps1 -Verbose | |
.EXAMPLE | |
.\Auto-Install-CertificatesInWSL.ps1 -DescriptionPatterns @("Zscaler", "Acme Corp") -WSLDistro "Ubuntu-20.04" -Verbose | |
.EXAMPLE | |
# Remote execution from GitHub Gist | |
&([ScriptBlock]::Create((irm https://gist.githubusercontent.com/emilwojcik93/7eb1e172f8bb038e324c6e4a7f4ccaaa/raw/Auto-Install-CertificatesInWSL.ps1))) -Verbose | |
.EXAMPLE | |
# Corporate environment with custom patterns | |
.\Auto-Install-CertificatesInWSL.ps1 -DescriptionPatterns @("MyCompany", "Proxy CA") -TestUrls @("https://internal.mycompany.com") -Verbose | |
.NOTES | |
Version: 3.0 | |
Author: Corporate Development Community | |
Requires: PowerShell 5.1+, WSL 2 | |
Universal script designed to work across different corporate environments. | |
Based on: https://gist.github.com/emilwojcik93/7eb1e172f8bb038e324c6e4a7f4ccaaa | |
Features: | |
- Generic/universal design with no hardcoded sensitive data | |
- Multi-pattern certificate detection | |
- Registry-based WSL distribution detection | |
- Improved WSL command syntax with proper path handling | |
- Optional intermediate certificate extraction | |
- Comprehensive environment variable setup | |
- Enhanced connectivity testing | |
- Parametrized options with sensible defaults | |
- Verbose logging and error handling | |
#> | |
[CmdletBinding()] | |
param( | |
[string[]]$DescriptionPatterns = @("CA", "Zscaler"), | |
[string[]]$ExcludeIssuers = @( | |
"Microsoft", "DigiCert", "VeriSign", "Thawte", "GeoTrust", "RapidSSL", | |
"Symantec", "Entrust", "GlobalSign", "Comodo", "StartCom", "GoDaddy", | |
"Let's Encrypt", "ISRG", "USERTrust", "AddTrust", "Certum", "QuoVadis", | |
"SSL.com", "Starfield", "IdenTrust", "Amazon", "Google Trust Services", | |
"Sectigo", "COMODO", "Baltimore", "UTN-USERFirst", "AAA Certificate Services" | |
), | |
[string]$WSLDistro = "", | |
[switch]$SetupNodeJS, | |
[bool]$ExtractIntermediate = $true, | |
[string[]]$TestUrls = @( | |
"https://google.com", | |
"https://github.com", | |
"https://www.microsoft.com", | |
"https://api.github.com", | |
"https://stackoverflow.com", | |
"https://www.npmjs.com" | |
), | |
[string]$CertificateDirectory = (Join-Path $env:USERPROFILE "certificates"), | |
[hashtable]$CustomWSLPaths = @{}, | |
[bool]$CreateBundle = $true, | |
[switch]$SetupEnvironmentVars, | |
[bool]$SkipWSLInstall = $false, | |
[switch]$ShowWSLInfo, | |
[switch]$InstallAllDistros, | |
[switch]$OverwriteExisting, | |
[switch]$SetupWSLEnvironment, | |
[bool]$TestBeforeAndAfter = $true, | |
[string]$LogFile = "", | |
[switch]$EnableLogging | |
) | |
# Suppress PowerShell progress bars for cleaner output | |
$ProgressPreference = 'SilentlyContinue' | |
$ErrorActionPreference = 'Continue' | |
# WSL distribution configurations - supports major Linux distributions | |
# Based on official WSL distribution names from 'wsl --list --online' | |
$WSLDistroConfigs = @{ | |
# Ubuntu family (Debian-based) | |
"Ubuntu" = @{ UpdateCommand = "update-ca-certificates"; CertPath = "/usr/local/share/ca-certificates/"; FileExt = ".crt" } | |
"Ubuntu-20.04" = @{ UpdateCommand = "update-ca-certificates"; CertPath = "/usr/local/share/ca-certificates/"; FileExt = ".crt" } | |
"Ubuntu-22.04" = @{ UpdateCommand = "update-ca-certificates"; CertPath = "/usr/local/share/ca-certificates/"; FileExt = ".crt" } | |
"Ubuntu-24.04" = @{ UpdateCommand = "update-ca-certificates"; CertPath = "/usr/local/share/ca-certificates/"; FileExt = ".crt" } | |
"Debian" = @{ UpdateCommand = "update-ca-certificates"; CertPath = "/usr/local/share/ca-certificates/"; FileExt = ".crt" } | |
"kali-linux" = @{ UpdateCommand = "update-ca-certificates"; CertPath = "/usr/local/share/ca-certificates/"; FileExt = ".crt" } | |
# Arch Linux family | |
"archlinux" = @{ UpdateCommand = "update-ca-trust"; CertPath = "/etc/ca-certificates/trust-source/anchors/"; FileExt = ".crt" } | |
# SUSE family | |
"openSUSE-Tumbleweed" = @{ UpdateCommand = "update-ca-certificates"; CertPath = "/etc/ssl/certs/"; FileExt = ".pem" } | |
"openSUSE-Leap-15.6" = @{ UpdateCommand = "update-ca-certificates"; CertPath = "/etc/ssl/certs/"; FileExt = ".pem" } | |
"SUSE-Linux-Enterprise-15-SP6" = @{ UpdateCommand = "update-ca-certificates"; CertPath = "/etc/ssl/certs/"; FileExt = ".pem" } | |
"SUSE-Linux-Enterprise-15-SP7" = @{ UpdateCommand = "update-ca-certificates"; CertPath = "/etc/ssl/certs/"; FileExt = ".pem" } | |
# RHEL family (Red Hat-based) | |
"FedoraLinux-42" = @{ UpdateCommand = "update-ca-trust"; CertPath = "/etc/pki/ca-trust/source/anchors/"; FileExt = ".crt" } | |
"AlmaLinux-8" = @{ UpdateCommand = "update-ca-trust"; CertPath = "/etc/pki/ca-trust/source/anchors/"; FileExt = ".crt" } | |
"AlmaLinux-9" = @{ UpdateCommand = "update-ca-trust"; CertPath = "/etc/pki/ca-trust/source/anchors/"; FileExt = ".crt" } | |
"AlmaLinux-10" = @{ UpdateCommand = "update-ca-trust"; CertPath = "/etc/pki/ca-trust/source/anchors/"; FileExt = ".crt" } | |
"AlmaLinux-Kitten-10" = @{ UpdateCommand = "update-ca-trust"; CertPath = "/etc/pki/ca-trust/source/anchors/"; FileExt = ".crt" } | |
"OracleLinux_7_9" = @{ UpdateCommand = "update-ca-trust"; CertPath = "/etc/pki/ca-trust/source/anchors/"; FileExt = ".crt" } | |
"OracleLinux_8_10" = @{ UpdateCommand = "update-ca-trust"; CertPath = "/etc/pki/ca-trust/source/anchors/"; FileExt = ".crt" } | |
"OracleLinux_9_5" = @{ UpdateCommand = "update-ca-trust"; CertPath = "/etc/pki/ca-trust/source/anchors/"; FileExt = ".crt" } | |
# Legacy/Common aliases (for backward compatibility) | |
"CentOS" = @{ UpdateCommand = "update-ca-trust"; CertPath = "/etc/pki/ca-trust/source/anchors/"; FileExt = ".crt" } | |
"RHEL" = @{ UpdateCommand = "update-ca-trust"; CertPath = "/etc/pki/ca-trust/source/anchors/"; FileExt = ".crt" } | |
"Fedora" = @{ UpdateCommand = "update-ca-trust"; CertPath = "/etc/pki/ca-trust/source/anchors/"; FileExt = ".crt" } | |
"AlmaLinux" = @{ UpdateCommand = "update-ca-trust"; CertPath = "/etc/pki/ca-trust/source/anchors/"; FileExt = ".crt" } | |
"Rocky" = @{ UpdateCommand = "update-ca-trust"; CertPath = "/etc/pki/ca-trust/source/anchors/"; FileExt = ".crt" } | |
"openSUSE" = @{ UpdateCommand = "update-ca-certificates"; CertPath = "/etc/ssl/certs/"; FileExt = ".pem" } | |
"SLES" = @{ UpdateCommand = "update-ca-certificates"; CertPath = "/etc/ssl/certs/"; FileExt = ".pem" } | |
"Arch" = @{ UpdateCommand = "update-ca-trust"; CertPath = "/etc/ca-certificates/trust-source/anchors/"; FileExt = ".crt" } | |
} | |
#region Helper Functions | |
function Write-Status { | |
<# | |
.SYNOPSIS | |
Writes formatted status messages with timestamps and color coding, optionally to log file | |
#> | |
param( | |
[Parameter(Mandatory)] | |
[AllowEmptyString()] | |
[string]$Message, | |
[ValidateSet("INFO", "SUCCESS", "WARNING", "ERROR", "VERBOSE")] | |
[string]$Type = "INFO" | |
) | |
# Handle empty messages by just writing a blank line | |
if ([string]::IsNullOrEmpty($Message)) { | |
Write-Host "" | |
if ($script:LogFilePath) { | |
Add-Content -Path $script:LogFilePath -Value "" -Encoding UTF8 | |
} | |
return | |
} | |
$timestamp = Get-Date -Format "HH:mm:ss" | |
$fullTimestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" | |
$prefix = switch ($Type) { | |
"SUCCESS" { "[SUCCESS]" } | |
"WARNING" { "[WARNING]" } | |
"ERROR" { "[ERROR]" } | |
"VERBOSE" { "[VERBOSE]" } | |
default { "[INFO]" } | |
} | |
$color = switch ($Type) { | |
"SUCCESS" { "Green" } | |
"WARNING" { "Yellow" } | |
"ERROR" { "Red" } | |
"VERBOSE" { "Gray" } | |
default { "Cyan" } | |
} | |
$consoleMessage = "[$timestamp] $prefix $Message" | |
$logMessage = "[$fullTimestamp] $prefix $Message" | |
# Write to console | |
Write-Host $consoleMessage -ForegroundColor $color | |
# Write to log file if logging is enabled | |
if ($script:LogFilePath) { | |
try { | |
Add-Content -Path $script:LogFilePath -Value $logMessage -Encoding UTF8 | |
} | |
catch { | |
# Don't fail if logging fails | |
Write-Host "[ERROR] Failed to write to log file: $($_.Exception.Message)" -ForegroundColor Red | |
} | |
} | |
} | |
function Get-WSLDistributionsFromRegistry { | |
<# | |
.SYNOPSIS | |
Retrieves all WSL distributions from Windows registry with detailed information | |
.DESCRIPTION | |
Queries the Windows registry to get comprehensive WSL distribution information, | |
including distribution names, states, versions, and package details. | |
This is more reliable than parsing WSL command output. | |
#> | |
Write-Verbose "Detecting WSL distributions from registry..." | |
try { | |
$registryPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss" | |
if (-not (Test-Path $registryPath)) { | |
Write-Status "WSL registry path not found - WSL may not be installed" "WARNING" | |
return @() | |
} | |
$distributions = @() | |
# Get default distribution GUID | |
$defaultGuid = Get-ItemProperty -Path $registryPath -Name DefaultDistribution -ErrorAction SilentlyContinue | | |
Select-Object -ExpandProperty DefaultDistribution | |
# Get all distribution GUIDs | |
$distroGuids = Get-ChildItem -Path $registryPath -ErrorAction SilentlyContinue | | |
Where-Object { $_.PSChildName -match '^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$' } | |
foreach ($guidEntry in $distroGuids) { | |
$distroPath = $guidEntry.PSPath | |
$distroInfo = Get-ItemProperty -Path $distroPath -ErrorAction SilentlyContinue | |
if ($distroInfo -and $distroInfo.DistributionName) { | |
$distribution = @{ | |
Name = $distroInfo.DistributionName | |
GUID = $guidEntry.PSChildName | |
IsDefault = ($guidEntry.PSChildName -eq $defaultGuid) | |
State = $distroInfo.State # 1 = Running, 0 = Stopped | |
Version = $distroInfo.Version # WSL version (1 or 2) | |
DefaultUid = $distroInfo.DefaultUid | |
BasePath = $distroInfo.BasePath | |
PackageFamilyName = $distroInfo.PackageFamilyName | |
DistributionFamily = Get-DistributionFamily -DistributionName $distroInfo.DistributionName | |
} | |
$distributions += $distribution | |
Write-Verbose "Found WSL distribution: $($distribution.Name) (Version: $($distribution.Version), State: $($distribution.State))" | |
} | |
} | |
Write-Status "Found $($distributions.Count) WSL distributions in registry" "SUCCESS" | |
return $distributions | |
} | |
catch { | |
Write-Status "Error detecting WSL distributions from registry: $($_.Exception.Message)" "ERROR" | |
return @() | |
} | |
} | |
function Get-DistributionFamily { | |
<# | |
.SYNOPSIS | |
Determines the distribution family from the distribution name | |
#> | |
param( | |
[Parameter(Mandatory)] | |
[string]$DistributionName | |
) | |
# Determine distribution family for certificate management | |
switch -Regex ($DistributionName) { | |
'^Ubuntu.*|^Debian$|^kali-linux$' { return "Debian" } | |
'^AlmaLinux.*|^FedoraLinux.*|^OracleLinux.*|^CentOS.*|^RHEL.*|^Rocky.*|^Fedora$' { return "RHEL" } | |
'^openSUSE.*|^SUSE.*|^SLES$' { return "SUSE" } | |
'^archlinux$|^Arch$' { return "Arch" } | |
default { return "Unknown" } | |
} | |
} | |
function Get-DefaultWSLDistro { | |
<# | |
.SYNOPSIS | |
Gets the default WSL distribution using registry information | |
#> | |
Write-Verbose "Getting default WSL distribution from registry..." | |
$distributions = Get-WSLDistributionsFromRegistry | |
if ($distributions.Count -eq 0) { | |
Write-Status "No WSL distributions found" "ERROR" | |
return $null | |
} | |
# Find default distribution | |
$defaultDistro = $distributions | Where-Object { $_.IsDefault } | |
if ($defaultDistro) { | |
Write-Status "Detected default WSL distribution: $($defaultDistro.Name) (Family: $($defaultDistro.DistributionFamily))" "SUCCESS" | |
return $defaultDistro.Name | |
} | |
# If no default set, use first available running distribution | |
$runningDistro = $distributions | Where-Object { $_.State -eq 1 } | Select-Object -First 1 | |
if ($runningDistro) { | |
Write-Status "Using running WSL distribution: $($runningDistro.Name) (Family: $($runningDistro.DistributionFamily))" "SUCCESS" | |
return $runningDistro.Name | |
} | |
# Otherwise use first available distribution | |
$firstDistro = $distributions | Select-Object -First 1 | |
Write-Status "Using first available WSL distribution: $($firstDistro.Name) (Family: $($firstDistro.DistributionFamily))" "SUCCESS" | |
return $firstDistro.Name | |
} | |
function Test-WSLAvailability { | |
<# | |
.SYNOPSIS | |
Verifies WSL availability and distribution installation using registry | |
#> | |
Write-Verbose "Testing WSL availability..." | |
try { | |
# Check if WSL registry path exists | |
$registryPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss" | |
if (-not (Test-Path $registryPath)) { | |
Write-Status "WSL is not installed or not available (registry path missing)" "ERROR" | |
return $false | |
} | |
# Get distributions from registry | |
$distributions = Get-WSLDistributionsFromRegistry | |
if ($distributions.Count -eq 0) { | |
Write-Status "No WSL distributions are installed" "ERROR" | |
return $false | |
} | |
Write-Status "WSL is available with $($distributions.Count) installed distribution(s)" "SUCCESS" | |
# Show available distributions in verbose mode | |
if ($VerbosePreference -eq 'Continue') { | |
Write-Status "Available WSL distributions:" "INFO" | |
foreach ($distro in $distributions) { | |
$status = if ($distro.State -eq 1) { "Running" } else { "Stopped" } | |
$default = if ($distro.IsDefault) { " (Default)" } else { "" } | |
Write-Host " - $($distro.Name): $status, WSL$($distro.Version)$default" -ForegroundColor Gray | |
} | |
} | |
return $true | |
} | |
catch { | |
Write-Status "Error testing WSL availability: $($_.Exception.Message)" "ERROR" | |
return $false | |
} | |
} | |
function Show-WSLDistributionInfo { | |
<# | |
.SYNOPSIS | |
Displays detailed information about installed WSL distributions | |
#> | |
$distributions = Get-WSLDistributionsFromRegistry | |
if ($distributions.Count -eq 0) { | |
Write-Status "No WSL distributions found" "WARNING" | |
return | |
} | |
Write-Status "Installed WSL Distributions:" "INFO" | |
Write-Status "============================" "INFO" | |
foreach ($distro in $distributions) { | |
$status = if ($distro.State -eq 1) { "Running" } else { "Stopped" } | |
$default = if ($distro.IsDefault) { " (Default)" } else { "" } | |
Write-Host "Distribution: $($distro.Name)$default" -ForegroundColor Cyan | |
Write-Host " Family: $($distro.DistributionFamily)" -ForegroundColor Gray | |
Write-Host " Status: $status" -ForegroundColor Gray | |
Write-Host " WSL Version: $($distro.Version)" -ForegroundColor Gray | |
Write-Host " Default UID: $($distro.DefaultUid)" -ForegroundColor Gray | |
Write-Host " GUID: $($distro.GUID)" -ForegroundColor Gray | |
# Show certificate configuration if available | |
if ($WSLDistroConfigs.ContainsKey($distro.Name)) { | |
$config = $WSLDistroConfigs[$distro.Name] | |
Write-Host " Certificate Path: $($config.CertPath)" -ForegroundColor Gray | |
Write-Host " Update Command: $($config.UpdateCommand)" -ForegroundColor Gray | |
} else { | |
Write-Host " Certificate Config: Will use $($distro.DistributionFamily) family defaults" -ForegroundColor Yellow | |
} | |
Write-Host "" | |
} | |
} | |
function Test-WSLDistroAvailability { | |
<# | |
.SYNOPSIS | |
Tests if a specific WSL distribution is available and responsive | |
#> | |
param( | |
[Parameter(Mandatory)] | |
[string]$DistroName | |
) | |
Write-Verbose "Testing WSL distribution availability: $DistroName" | |
try { | |
# Test basic command execution | |
$testResult = wsl -d $DistroName -e bash -c "echo 'test'" 2>$null | |
if ($LASTEXITCODE -eq 0 -and $testResult -eq "test") { | |
Write-Verbose "WSL distribution $DistroName is responsive" | |
return $true | |
} else { | |
Write-Status "WSL distribution $DistroName is not responsive" "ERROR" | |
return $false | |
} | |
} | |
catch { | |
Write-Status "Error testing WSL distribution $DistroName : $($_.Exception.Message)" "ERROR" | |
return $false | |
} | |
} | |
function Install-WSLDependencies { | |
<# | |
.SYNOPSIS | |
Installs required dependencies in WSL distribution (curl, openssl) | |
#> | |
param( | |
[Parameter(Mandatory)] | |
[string]$DistroName | |
) | |
Write-Status "Checking and installing dependencies in WSL distribution: $DistroName" "INFO" | |
try { | |
# Test curl availability | |
$curlTest = wsl -d $DistroName -u root -e bash -c "which curl" 2>$null | |
if ($LASTEXITCODE -ne 0 -or -not $curlTest) { | |
Write-Status "curl not found, attempting to install..." "WARNING" | |
# Detect package manager and install curl | |
$aptResult = wsl -d $DistroName -u root -e bash -c "which apt-get" 2>$null | |
$yumResult = wsl -d $DistroName -u root -e bash -c "which yum" 2>$null | |
$dnfResult = wsl -d $DistroName -u root -e bash -c "which dnf" 2>$null | |
$pacmanResult = wsl -d $DistroName -u root -e bash -c "which pacman" 2>$null | |
$zypperResult = wsl -d $DistroName -u root -e bash -c "which zypper" 2>$null | |
if ($LASTEXITCODE -eq 0 -and $aptResult) { | |
wsl -d $DistroName -u root -e bash -c "apt-get update && apt-get install -y curl openssl ca-certificates" 2>$null | |
} elseif ($LASTEXITCODE -eq 0 -and $dnfResult) { | |
wsl -d $DistroName -u root -e bash -c "dnf install -y curl openssl ca-certificates" 2>$null | |
} elseif ($LASTEXITCODE -eq 0 -and $yumResult) { | |
wsl -d $DistroName -u root -e bash -c "yum install -y curl openssl ca-certificates" 2>$null | |
} elseif ($LASTEXITCODE -eq 0 -and $pacmanResult) { | |
wsl -d $DistroName -u root -e bash -c "pacman -Sy --noconfirm curl openssl ca-certificates" 2>$null | |
} elseif ($LASTEXITCODE -eq 0 -and $zypperResult) { | |
wsl -d $DistroName -u root -e bash -c "zypper install -y curl openssl ca-certificates" 2>$null | |
} else { | |
Write-Status "Could not detect package manager to install curl" "ERROR" | |
return $false | |
} | |
if ($LASTEXITCODE -eq 0) { | |
Write-Status "Dependencies installed successfully" "SUCCESS" | |
return $true | |
} else { | |
Write-Status "Failed to install dependencies" "ERROR" | |
return $false | |
} | |
} else { | |
Write-Verbose "curl is available in $DistroName" | |
return $true | |
} | |
} | |
catch { | |
Write-Status "Error installing dependencies: $($_.Exception.Message)" "ERROR" | |
return $false | |
} | |
} | |
function Search-CorporateCertificates { | |
<# | |
.SYNOPSIS | |
Searches for certificates matching specified patterns and exclusions | |
#> | |
param( | |
[Parameter(Mandatory)] | |
[string[]]$DescriptionPatterns, | |
[Parameter(Mandatory)] | |
[string[]]$ExcludeIssuers | |
) | |
Write-Status "Searching for certificates matching patterns: $($DescriptionPatterns -join ', ')" "INFO" | |
# Define certificate stores to search | |
$certificateStores = @( | |
"Cert:\LocalMachine\Root", | |
"Cert:\LocalMachine\CA", | |
"Cert:\LocalMachine\AuthRoot", | |
"Cert:\LocalMachine\TrustedPublisher", | |
"Cert:\LocalMachine\TrustedPeople", | |
"Cert:\LocalMachine\My", | |
"Cert:\CurrentUser\Root", | |
"Cert:\CurrentUser\CA", | |
"Cert:\CurrentUser\AuthRoot", | |
"Cert:\CurrentUser\TrustedPublisher", | |
"Cert:\CurrentUser\TrustedPeople", | |
"Cert:\CurrentUser\My" | |
) | |
$matchedCertificates = @() | |
$processedThumbprints = @{} | |
foreach ($store in $certificateStores) { | |
Write-Verbose "Searching certificates in store: $store" | |
try { | |
$certificates = Get-ChildItem -Path $store -ErrorAction SilentlyContinue | |
foreach ($cert in $certificates) { | |
# Skip if we've already processed this certificate (by thumbprint) | |
if ($processedThumbprints.ContainsKey($cert.Thumbprint)) { | |
continue | |
} | |
# Check if certificate matches any of the description patterns | |
$matchesPattern = $false | |
$matchedPattern = "" | |
foreach ($pattern in $DescriptionPatterns) { | |
if ($cert.Subject -like "*$pattern*" -or $cert.Issuer -like "*$pattern*") { | |
$matchesPattern = $true | |
$matchedPattern = $pattern | |
break | |
} | |
} | |
if ($matchesPattern) { | |
# Check if certificate should be excluded | |
$shouldExclude = $false | |
foreach ($excludeIssuer in $ExcludeIssuers) { | |
if ($cert.Issuer -like "*$excludeIssuer*" -or $cert.Subject -like "*$excludeIssuer*") { | |
$shouldExclude = $true | |
Write-Verbose "Excluding certificate with issuer/subject containing: $excludeIssuer" | |
break | |
} | |
} | |
if (-not $shouldExclude) { | |
Write-Verbose "Matched certificate: $($cert.PSPath)" | |
Write-Verbose " Subject: $($cert.Subject)" | |
Write-Verbose " Issuer: $($cert.Issuer)" | |
Write-Verbose " Expires: $($cert.NotAfter)" | |
Write-Verbose " Store: $store" | |
Write-Verbose " Matched Pattern: $matchedPattern" | |
$matchedCertificates += @{ | |
Certificate = $cert | |
Store = $store | |
MatchedPattern = $matchedPattern | |
} | |
# Mark as processed | |
$processedThumbprints[$cert.Thumbprint] = $true | |
} | |
} | |
} | |
} | |
catch { | |
Write-Verbose "Could not access certificate store $store : $($_.Exception.Message)" | |
} | |
} | |
Write-Status "Found $($matchedCertificates.Count) unique matching certificates" "SUCCESS" | |
return $matchedCertificates | |
} | |
function Export-CertificateToFile { | |
<# | |
.SYNOPSIS | |
Exports a certificate to a PEM file with proper formatting and overwrite handling | |
#> | |
param( | |
[Parameter(Mandatory)] | |
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate, | |
[Parameter(Mandatory)] | |
[string]$FilePath | |
) | |
try { | |
# Check if file exists and handle overwrite | |
if (Test-Path $FilePath) { | |
if ($OverwriteExisting) { | |
Write-Verbose "Overwriting existing certificate file: $FilePath" | |
} else { | |
Write-Status "Certificate file already exists: $(Split-Path -Leaf $FilePath) (use -OverwriteExisting to overwrite)" "WARNING" | |
return $true # Skip but don't fail | |
} | |
} | |
# Export certificate as Base64 encoded bytes | |
$bytes = $Certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) | |
$base64 = [Convert]::ToBase64String($bytes) | |
# Wrap base64 to 64 characters per line (PEM standard) | |
$wrapped = ($base64 -split '(.{64})' | Where-Object { $_ }) -join "`n" | |
# Create PEM format with proper headers | |
$pemContent = "-----BEGIN CERTIFICATE-----`n$wrapped`n-----END CERTIFICATE-----" | |
# Write to file with UTF8 encoding | |
Set-Content -Path $FilePath -Value $pemContent -Encoding UTF8 | |
Write-Verbose "Exported certificate to file: $FilePath" | |
return $true | |
} | |
catch { | |
Write-Status "Failed to export certificate to $FilePath : $($_.Exception.Message)" "ERROR" | |
return $false | |
} | |
} | |
function Get-SafeFileName { | |
<# | |
.SYNOPSIS | |
Creates a safe filename from certificate subject | |
#> | |
param( | |
[Parameter(Mandatory)] | |
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate, | |
[string]$MatchedPattern = "", | |
[int]$Index = 0 | |
) | |
try { | |
# Extract CN from subject | |
$subject = $Certificate.Subject | |
$cn = "" | |
# Try to extract CN (Common Name) | |
if ($subject -match "CN=([^,]+)") { | |
$cn = $Matches[1].Trim() | |
} | |
# If no CN found, use the matched pattern or generic name | |
if (-not $cn) { | |
$cn = if ($MatchedPattern) { $MatchedPattern } else { "Certificate" } | |
} | |
# Clean the name for filesystem compatibility | |
$safeName = $cn -replace '[^\w\s\-\.]', '_' -replace '\s+', '_' -replace '_+', '_' | |
$safeName = $safeName.Trim('_') | |
# Add pattern prefix if available | |
if ($MatchedPattern -and $cn -notlike "*$MatchedPattern*") { | |
$safeName = "${MatchedPattern}_${safeName}" | |
} | |
# Add index if provided (for uniqueness) | |
if ($Index -gt 0) { | |
$safeName = "${safeName}_${Index}" | |
} | |
# Ensure reasonable length | |
if ($safeName.Length -gt 100) { | |
$safeName = $safeName.Substring(0, 100) | |
} | |
return "${safeName}.crt" | |
} | |
catch { | |
# Fallback to generic name | |
$fallbackName = if ($MatchedPattern) { "${MatchedPattern}_Certificate" } else { "Certificate" } | |
if ($Index -gt 0) { | |
$fallbackName = "${fallbackName}_${Index}" | |
} | |
return "${fallbackName}.crt" | |
} | |
} | |
function Remove-OldCertificatesFromWSL { | |
<# | |
.SYNOPSIS | |
Removes old certificates from WSL certificate store | |
#> | |
param( | |
[Parameter(Mandatory)] | |
[string]$DistroName, | |
[Parameter(Mandatory)] | |
[string]$CertPath | |
) | |
try { | |
Write-Verbose "Removing old certificates from WSL distribution: $DistroName" | |
# Remove old certificates matching common corporate patterns | |
$removeCommand = "find '$CertPath' -name '*.crt' -exec rm -f {} \; 2>/dev/null || true" | |
wsl --cd "/" -d $DistroName -u root -e bash -c $removeCommand 2>$null | |
Write-Verbose "Removed old certificates from WSL" | |
return $true | |
} | |
catch { | |
Write-Verbose "Could not remove old certificates (this is normal): $($_.Exception.Message)" | |
return $false | |
} | |
} | |
function Install-CertificateInWSL { | |
<# | |
.SYNOPSIS | |
Installs a certificate in the specified WSL distribution using improved syntax | |
#> | |
param( | |
[Parameter(Mandatory)] | |
[string]$CertFilePath, | |
[Parameter(Mandatory)] | |
[string]$DistroName, | |
[Parameter(Mandatory)] | |
[hashtable]$DistroConfig | |
) | |
$certFileName = Split-Path -Leaf $CertFilePath | |
$certFileNameWithExt = [System.IO.Path]::GetFileNameWithoutExtension($certFileName) + $DistroConfig.FileExt | |
Write-Verbose "Installing certificate $certFileName in WSL distribution: $DistroName" | |
try { | |
# Convert Windows path to WSL path | |
$wslSourcePath = $CertFilePath -replace '^C:', '/mnt/c' -replace '\\', '/' | |
$wslDestPath = $DistroConfig.CertPath + $certFileNameWithExt | |
# Ensure certificate directory exists | |
$mkdirCommand = "mkdir -p '$(Split-Path -Path $wslDestPath -Parent)'" | |
wsl --cd "/" -d $DistroName -u root -e bash -c $mkdirCommand 2>$null | |
# Copy certificate to WSL using improved syntax | |
$copyCommand = "cp '$wslSourcePath' '$wslDestPath'" | |
wsl --cd "$(Split-Path -Path $CertFilePath -Parent)" -d $DistroName -u root -e bash -c $copyCommand 2>$null | Out-Null | |
if ($LASTEXITCODE -ne 0) { | |
Write-Status "Failed to copy certificate $certFileName to WSL" "ERROR" | |
return $false | |
} | |
Write-Verbose "Certificate copied to $wslDestPath" | |
# Verify certificate was copied successfully | |
$verifyCommand = "test -f '$wslDestPath' && echo 'EXISTS' || echo 'MISSING'" | |
$verifyResult = wsl --cd "/" -d $DistroName -u root -e bash -c $verifyCommand 2>$null | |
if ($verifyResult -like "*EXISTS*") { | |
Write-Status "Certificate installed: $certFileNameWithExt" "SUCCESS" | |
return $true | |
} else { | |
Write-Status "Certificate copy verification failed for: $certFileName" "ERROR" | |
return $false | |
} | |
} | |
catch { | |
Write-Status "Error installing certificate $certFileName in WSL: $($_.Exception.Message)" "ERROR" | |
return $false | |
} | |
} | |
function Update-WSLCertificateStore { | |
<# | |
.SYNOPSIS | |
Updates the WSL certificate store using the appropriate command for the distribution | |
#> | |
param( | |
[Parameter(Mandatory)] | |
[string]$DistroName, | |
[Parameter(Mandatory)] | |
[hashtable]$DistroConfig | |
) | |
Write-Status "Updating certificate store in WSL distribution: $DistroName" "INFO" | |
try { | |
# Test if update command is available | |
$commandCheck = switch ($DistroConfig.UpdateCommand) { | |
"update-ca-certificates" { "which update-ca-certificates >/dev/null 2>&1 && echo 'AVAILABLE' || echo 'MISSING'" } | |
"update-ca-trust" { "which update-ca-trust >/dev/null 2>&1 && echo 'AVAILABLE' || echo 'MISSING'" } | |
"trust extract-compat" { "which trust >/dev/null 2>&1 && echo 'AVAILABLE' || echo 'MISSING'" } | |
default { "echo 'UNKNOWN'" } | |
} | |
$commandAvailable = wsl --cd "/" -d $DistroName -u root -e bash -c $commandCheck 2>$null | |
Write-Verbose "Command availability check: $commandAvailable" | |
if ($commandAvailable -like "*AVAILABLE*") { | |
Write-Verbose "Executing certificate update command: $($DistroConfig.UpdateCommand)" | |
# Execute the appropriate update command | |
wsl --cd "/" -d $DistroName -u root -e bash -c $DistroConfig.UpdateCommand 2>$null | Out-Null | |
if ($LASTEXITCODE -eq 0) { | |
Write-Status "Certificate store updated successfully in WSL" "SUCCESS" | |
return $true | |
} else { | |
Write-Status "Certificate store update command failed (exit code: $LASTEXITCODE)" "ERROR" | |
return $false | |
} | |
} else { | |
Write-Status "$($DistroConfig.UpdateCommand) is not available in WSL distribution $DistroName" "ERROR" | |
return $false | |
} | |
} | |
catch { | |
Write-Status "Error updating certificate store in WSL: $($_.Exception.Message)" "ERROR" | |
return $false | |
} | |
} | |
function Test-HTTPSConnectivity { | |
<# | |
.SYNOPSIS | |
Tests HTTPS connectivity from WSL using curl with enhanced error handling | |
#> | |
param( | |
[Parameter(Mandatory)] | |
[string]$DistroName, | |
[Parameter(Mandatory)] | |
[string]$Url | |
) | |
try { | |
Write-Verbose "Testing HTTPS connectivity for $Url in WSL distribution: $DistroName" | |
# Use curl with comprehensive options for better testing | |
$curlCommand = "curl -s -L --max-time 15 --connect-timeout 10 --write-out '%{http_code}' --output /dev/null '$Url'" | |
$response = wsl --cd "/" -d $DistroName -u root -e bash -c $curlCommand 2>$null | |
Write-Verbose "Response from HTTPS test for ${Url}: $response (Exit code: $LASTEXITCODE)" | |
if ($LASTEXITCODE -eq 0 -and $response -match '^\d{3}$') { | |
# Consider SSL handshake successful if we get any HTTP response | |
# Even 4xx/5xx errors mean SSL worked, just application-level issues | |
$statusCode = [int]$response | |
$sslSuccess = ($statusCode -ge 200 -and $statusCode -lt 600) # Any valid HTTP response | |
return @{ | |
Success = $sslSuccess | |
StatusCode = $response | |
Error = $null | |
} | |
} else { | |
return @{ | |
Success = $false | |
StatusCode = $response | |
Error = "curl exit code: $LASTEXITCODE" | |
} | |
} | |
} | |
catch { | |
Write-Verbose "Error testing HTTPS connectivity for ${Url}: $($_.Exception.Message)" | |
return @{ | |
Success = $false | |
StatusCode = "000" | |
Error = $_.Exception.Message | |
} | |
} | |
} | |
function Test-ConnectivityForDistribution { | |
<# | |
.SYNOPSIS | |
Tests HTTPS connectivity for a specific distribution with detailed reporting | |
#> | |
param( | |
[Parameter(Mandatory)] | |
[string]$DistroName, | |
[string[]]$TestUrls, | |
[string]$TestPhase = "Test" | |
) | |
Write-Status "[$TestPhase] Testing HTTPS connectivity for $DistroName..." "INFO" | |
$results = @{} | |
$successCount = 0 | |
foreach ($url in $TestUrls) { | |
$result = Test-HTTPSConnectivity -DistroName $DistroName -Url $url | |
$results[$url] = $result | |
if ($result.Success) { | |
$successCount++ | |
$statusCode = $result.StatusCode | |
# Classify HTTP status codes more intelligently | |
# The key is that we got ANY HTTP response - this means SSL handshake succeeded | |
$statusType = switch ($statusCode) { | |
{ $_ -match '^(200|201|202)$' } { "SUCCESS" } # Perfect responses | |
{ $_ -match '^(301|302|303|307|308)$' } { "SUCCESS" } # Redirects (SSL worked) | |
{ $_ -match '^(403|405|429)$' } { "SUCCESS" } # Blocked but SSL worked | |
{ $_ -match '^(404|410)$' } { "SUCCESS" } # Not found but SSL worked | |
{ $_ -match '^(500|502|503|504)$' } { "WARNING" } # Server errors (SSL worked, server down) | |
default { "WARNING" } # Other codes (SSL worked) | |
} | |
$icon = switch ($statusType) { | |
"SUCCESS" { "[PASS]" } | |
"WARNING" { "[WARN]" } | |
default { "[INFO]" } | |
} | |
Write-Status "$icon $url (HTTP $statusCode)" $statusType | |
} else { | |
Write-Status "[FAIL] $url (Failed: $($result.Error))" "ERROR" | |
} | |
} | |
$successRate = [Math]::Round(($successCount / $TestUrls.Count) * 100, 1) | |
if ($successRate -eq 100) { | |
Write-Status "[$TestPhase] All HTTPS tests passed for $DistroName ($successRate%)" "SUCCESS" | |
} elseif ($successRate -gt 70) { | |
Write-Status "[$TestPhase] Most HTTPS tests passed for $DistroName ($successRate%)" "WARNING" | |
} else { | |
Write-Status "[$TestPhase] HTTPS connectivity issues detected for $DistroName ($successRate%)" "ERROR" | |
} | |
return @{ | |
Results = $results | |
SuccessCount = $successCount | |
TotalCount = $TestUrls.Count | |
SuccessRate = $successRate | |
} | |
} | |
function Get-IntermediateCertificate { | |
<# | |
.SYNOPSIS | |
Extracts intermediate certificates from live HTTPS connections using openssl | |
#> | |
param( | |
[string]$TestUrl = "https://google.com", | |
[string]$OutputPath, | |
[string]$DistroName | |
) | |
if (-not $ExtractIntermediate) { | |
Write-Verbose "Intermediate certificate extraction is disabled" | |
return $null | |
} | |
Write-Status "Attempting to extract intermediate certificate from $TestUrl" "INFO" | |
try { | |
# Extract hostname from URL | |
$hostname = $TestUrl -replace 'https://', '' -replace 'http://', '' -replace '/.*', '' | |
# Extract certificate chain using openssl in WSL | |
$opensslCommand = "echo | openssl s_client -connect ${hostname}:443 -servername $hostname -showcerts 2>/dev/null" | |
$chainOutput = wsl --cd "/" -d $DistroName -u root -e bash -c $opensslCommand 2>$null | |
if ($LASTEXITCODE -ne 0 -or -not $chainOutput) { | |
Write-Status "Failed to extract certificate chain from $TestUrl" "WARNING" | |
return $null | |
} | |
# Parse certificate chain to find intermediate certificate (usually 2nd certificate) | |
$lines = $chainOutput -split "`n" | |
$certCount = 0 | |
$inCert = $false | |
$intermediateCert = @() | |
foreach ($line in $lines) { | |
if ($line -match "-----BEGIN CERTIFICATE-----") { | |
$certCount++ | |
if ($certCount -eq 2) { # Second certificate is typically the intermediate | |
$inCert = $true | |
$intermediateCert = @($line) | |
} | |
} | |
elseif ($line -match "-----END CERTIFICATE-----" -and $inCert) { | |
$intermediateCert += $line | |
break | |
} | |
elseif ($inCert) { | |
$intermediateCert += $line | |
} | |
} | |
if ($intermediateCert.Count -gt 0) { | |
$certContent = $intermediateCert -join "`n" | |
$intermediateFileName = "Intermediate_CA_$(Get-Date -Format 'yyyyMMdd_HHmmss').crt" | |
$intermediatePath = Join-Path $OutputPath $intermediateFileName | |
Set-Content -Path $intermediatePath -Value $certContent -Encoding UTF8 | |
Write-Status "Extracted intermediate certificate: $intermediateFileName" "SUCCESS" | |
return $intermediatePath | |
} else { | |
Write-Status "Could not extract intermediate certificate from connection to $TestUrl" "WARNING" | |
return $null | |
} | |
} | |
catch { | |
Write-Status "Error extracting intermediate certificate: $($_.Exception.Message)" "ERROR" | |
return $null | |
} | |
} | |
function Set-NodeJSEnvironmentVariables { | |
<# | |
.SYNOPSIS | |
Configures Node.js and other development environment variables for certificate bundle usage | |
#> | |
param( | |
[Parameter(Mandatory)] | |
[string]$BundlePath | |
) | |
if (-not $SetupNodeJS) { | |
Write-Verbose "Node.js environment setup is disabled" | |
return | |
} | |
Write-Status "Configuring Windows Node.js environment variables" "INFO" | |
try { | |
# Core Node.js certificate environment variables | |
[Environment]::SetEnvironmentVariable("NODE_EXTRA_CA_CERTS", $BundlePath, "User") | |
[Environment]::SetEnvironmentVariable("NODE_TLS_REJECT_UNAUTHORIZED", "0", "User") | |
[Environment]::SetEnvironmentVariable("NODE_NO_WARNINGS", "1", "User") | |
Write-Status "Set NODE_EXTRA_CA_CERTS = $BundlePath" "SUCCESS" | |
Write-Status "Set NODE_TLS_REJECT_UNAUTHORIZED = 0" "SUCCESS" | |
Write-Status "Set NODE_NO_WARNINGS = 1" "SUCCESS" | |
# Additional SSL/TLS environment variables if requested | |
if ($SetupEnvironmentVars) { | |
[Environment]::SetEnvironmentVariable("SSL_CERT_FILE", $BundlePath, "User") | |
[Environment]::SetEnvironmentVariable("HTTPS_CA_BUNDLE", $BundlePath, "User") | |
[Environment]::SetEnvironmentVariable("REQUESTS_CA_BUNDLE", $BundlePath, "User") | |
[Environment]::SetEnvironmentVariable("CURL_CA_BUNDLE", $BundlePath, "User") | |
[Environment]::SetEnvironmentVariable("JFROG_CLI_CERT_FILE", $BundlePath, "User") | |
Write-Status "Set SSL_CERT_FILE = $BundlePath" "SUCCESS" | |
Write-Status "Set HTTPS_CA_BUNDLE = $BundlePath" "SUCCESS" | |
Write-Status "Set REQUESTS_CA_BUNDLE = $BundlePath" "SUCCESS" | |
Write-Status "Set CURL_CA_BUNDLE = $BundlePath" "SUCCESS" | |
Write-Status "Set JFROG_CLI_CERT_FILE = $BundlePath" "SUCCESS" | |
} | |
Write-Status "Windows environment variables configured successfully" "SUCCESS" | |
Write-Status "Restart your terminal/IDE to apply environment variable changes" "WARNING" | |
} | |
catch { | |
Write-Status "Error setting Windows environment variables: $($_.Exception.Message)" "ERROR" | |
return $false | |
} | |
} | |
function Set-WSLEnvironmentVariables { | |
<# | |
.SYNOPSIS | |
Configures environment variables inside WSL distributions for development tools | |
#> | |
param( | |
[Parameter(Mandatory)] | |
[string]$DistroName, | |
[Parameter(Mandatory)] | |
[string]$BundlePath | |
) | |
if (-not $SetupWSLEnvironment) { | |
Write-Verbose "WSL environment setup is disabled" | |
return | |
} | |
Write-Status "Configuring WSL environment variables for $DistroName" "INFO" | |
try { | |
# Convert Windows path to WSL path | |
$wslBundlePath = $BundlePath -replace '^C:', '/mnt/c' -replace '\\', '/' | |
# Create environment setup script | |
$envSetupScript = @" | |
# Corporate Certificate Environment Variables | |
export SSL_CERT_FILE='$wslBundlePath' | |
export HTTPS_CA_BUNDLE='$wslBundlePath' | |
export REQUESTS_CA_BUNDLE='$wslBundlePath' | |
export CURL_CA_BUNDLE='$wslBundlePath' | |
export NODE_EXTRA_CA_CERTS='$wslBundlePath' | |
export NODE_TLS_REJECT_UNAUTHORIZED='0' | |
export NODE_NO_WARNINGS='1' | |
export PYTHONHTTPSVERIFY='0' | |
export JFROG_CLI_CERT_FILE='$wslBundlePath' | |
export DOCKER_CERT_PATH='$wslBundlePath' | |
export PIP_CERT='$wslBundlePath' | |
export NPM_CONFIG_CAFILE='$wslBundlePath' | |
export YARN_CAFILE='$wslBundlePath' | |
"@ | |
# Write environment setup to WSL | |
$envFile = "/etc/profile.d/corporate-certs.sh" | |
$writeEnvCommand = "echo `"$envSetupScript`" | sudo tee `"$envFile`" > /dev/null && sudo chmod +x `"$envFile`"" | |
wsl --cd "/" -d $DistroName -u root -e bash -c $writeEnvCommand 2>$null | |
if ($LASTEXITCODE -eq 0) { | |
Write-Status "WSL environment variables configured in $envFile" "SUCCESS" | |
# Also add to common shell profiles | |
$shellProfiles = @(".bashrc", ".zshrc", ".profile") | |
foreach ($shellProfile in $shellProfiles) { | |
$profilePath = "/home/`$(whoami)/$shellProfile" | |
$sourceCommand = "echo 'source $envFile' >> `"$profilePath`" 2>/dev/null || true" | |
wsl --cd "/" -d $DistroName -e bash -c $sourceCommand 2>$null | |
} | |
Write-Status "Environment variables will be available in new WSL sessions" "SUCCESS" | |
return $true | |
} else { | |
Write-Status "Failed to configure WSL environment variables" "ERROR" | |
return $false | |
} | |
} | |
catch { | |
Write-Status "Error setting WSL environment variables: $($_.Exception.Message)" "ERROR" | |
return $false | |
} | |
} | |
function New-CertificateBundle { | |
<# | |
.SYNOPSIS | |
Creates a combined certificate bundle from exported certificates | |
#> | |
param( | |
[Parameter(Mandatory)] | |
[string]$CertificateDirectory, | |
[string]$BundleFileName = "Corporate_CA_Bundle.crt", | |
[string[]]$CertificateFiles = @() | |
) | |
if (-not $CreateBundle) { | |
Write-Verbose "Certificate bundle creation is disabled" | |
return $null | |
} | |
Write-Status "Creating certificate bundle: $BundleFileName" "INFO" | |
try { | |
$bundlePath = Join-Path $CertificateDirectory $BundleFileName | |
$bundleContent = @() | |
# Add header comment | |
$bundleContent += "# Corporate Certificate Bundle" | |
$bundleContent += "# Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" | |
$bundleContent += "# Contains corporate certificates for development environments" | |
$bundleContent += "" | |
# Use provided certificate files or discover them | |
if ($CertificateFiles.Count -gt 0) { | |
# Use the specific files provided | |
$certFilesToBundle = $CertificateFiles | ForEach-Object { | |
if (Test-Path $_) { | |
Get-Item $_ | |
} | |
} | |
} else { | |
# Fallback to discovering certificate files (excluding bundles) | |
$certFilesToBundle = Get-ChildItem -Path $CertificateDirectory -Filter "*.crt" | | |
Where-Object { | |
$_.Name -ne $BundleFileName -and | |
$_.Name -notlike "*Bundle*" -and | |
$_.Name -notlike "*Combined*" | |
} | | |
Sort-Object Name | |
} | |
if ($certFilesToBundle.Count -eq 0) { | |
Write-Status "No certificate files found to bundle" "WARNING" | |
return $null | |
} | |
foreach ($certFile in $certFilesToBundle) { | |
Write-Verbose "Adding certificate to bundle: $($certFile.Name)" | |
$content = Get-Content $certFile.FullName -Raw | |
# Add certificate name comment | |
$bundleContent += "# Certificate: $($certFile.Name)" | |
$bundleContent += $content.Trim() | |
$bundleContent += "" | |
} | |
# Write bundle to file | |
$bundleContent -join "`n" | Set-Content -Path $bundlePath -Encoding UTF8 | |
$bundleSize = [Math]::Round((Get-Item $bundlePath).Length / 1KB, 1) | |
Write-Status "Created certificate bundle: $BundleFileName ($bundleSize KB)" "SUCCESS" | |
Write-Status "Bundle contains $($certFilesToBundle.Count) certificates" "SUCCESS" | |
return $bundlePath | |
} | |
catch { | |
Write-Status "Error creating certificate bundle: $($_.Exception.Message)" "ERROR" | |
return $null | |
} | |
} | |
#endregion | |
#region Main Function | |
function Invoke-Main { | |
<# | |
.SYNOPSIS | |
Main execution function that orchestrates the entire certificate installation process | |
#> | |
# Initialize logging if requested | |
$script:LogFilePath = $null | |
if ($EnableLogging -or $LogFile) { | |
if (-not $LogFile) { | |
$LogFile = Join-Path $env:TEMP "Auto-Install-CertificatesInWSL_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" | |
} | |
try { | |
# Create log file directory if it doesn't exist | |
$logDir = Split-Path -Path $LogFile -Parent | |
if ($logDir -and -not (Test-Path $logDir)) { | |
New-Item -ItemType Directory -Path $logDir -Force | Out-Null | |
} | |
# Initialize log file | |
$script:LogFilePath = $LogFile | |
$logHeader = @" | |
================================================================= | |
Universal Corporate Certificate Installer for WSL v3.1 | |
Log started: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | |
================================================================= | |
"@ | |
Set-Content -Path $script:LogFilePath -Value $logHeader -Encoding UTF8 | |
Write-Host "[INFO] Logging enabled: $script:LogFilePath" -ForegroundColor Cyan | |
} | |
catch { | |
Write-Host "[WARNING] Failed to initialize logging: $($_.Exception.Message)" -ForegroundColor Yellow | |
$script:LogFilePath = $null | |
} | |
} | |
Write-Status "===================================================================" | |
Write-Status " Universal Corporate Certificate Installer for WSL v3.1" | |
Write-Status "===================================================================" | |
Write-Status "Started: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" | |
Write-Status "" | |
# Show WSL distribution information if requested | |
if ($ShowWSLInfo) { | |
Show-WSLDistributionInfo | |
return | |
} | |
# Create certificate directory if it doesn't exist | |
if (-not (Test-Path $CertificateDirectory)) { | |
try { | |
New-Item -ItemType Directory -Path $CertificateDirectory -Force | Out-Null | |
Write-Status "Created certificate directory: $CertificateDirectory" "SUCCESS" | |
} | |
catch { | |
Write-Status "Failed to create certificate directory: $($_.Exception.Message)" "ERROR" | |
return | |
} | |
} else { | |
Write-Status "Using existing certificate directory: $CertificateDirectory" "INFO" | |
} | |
# Test WSL availability (skip if only exporting certificates) | |
if (-not $SkipWSLInstall) { | |
if (-not (Test-WSLAvailability)) { | |
Write-Status "WSL is not available. Please install WSL and at least one distribution." "ERROR" | |
Write-Status "Alternatively, use -SkipWSLInstall to export certificates only." "INFO" | |
return | |
} | |
} | |
# Search for corporate certificates | |
Write-Status "Searching for corporate certificates..." "INFO" | |
$certificateMatches = Search-CorporateCertificates -DescriptionPatterns $DescriptionPatterns -ExcludeIssuers $ExcludeIssuers | |
if ($certificateMatches.Count -eq 0) { | |
Write-Status "No matching certificates found for patterns: $($DescriptionPatterns -join ', ')" "WARNING" | |
Write-Status "Please verify that corporate certificates are installed and patterns are correct" "WARNING" | |
Write-Status "You may need to adjust the -DescriptionPatterns parameter" "INFO" | |
return | |
} | |
# Export certificates to files | |
Write-Status "Exporting certificates to files..." "INFO" | |
$exportedCerts = @() | |
$exportIndex = 0 | |
foreach ($certMatch in $certificateMatches) { | |
$exportIndex++ | |
$cert = $certMatch.Certificate | |
$matchedPattern = $certMatch.MatchedPattern | |
$fileName = Get-SafeFileName -Certificate $cert -MatchedPattern $matchedPattern -Index $exportIndex | |
$filePath = Join-Path $CertificateDirectory $fileName | |
if (Export-CertificateToFile -Certificate $cert -FilePath $filePath) { | |
Write-Status "Exported: $fileName" "SUCCESS" | |
$exportedCerts += $filePath | |
Write-Verbose " Subject: $($cert.Subject)" | |
Write-Verbose " Issuer: $($cert.Issuer)" | |
Write-Verbose " Store: $($certMatch.Store)" | |
Write-Verbose " Pattern: $matchedPattern" | |
} | |
} | |
if ($exportedCerts.Count -eq 0) { | |
Write-Status "No certificates were successfully exported" "ERROR" | |
return | |
} | |
# Extract intermediate certificate if requested | |
$intermediateCertPath = $null | |
if ($ExtractIntermediate -and -not $SkipWSLInstall) { | |
# Use first available distribution for intermediate certificate extraction | |
$firstDistro = if ($WSLDistro) { $WSLDistro } else { (Get-WSLDistributionsFromRegistry)[0].Name } | |
if ($firstDistro) { | |
$intermediateCertPath = Get-IntermediateCertificate -TestUrl $TestUrls[0] -OutputPath $CertificateDirectory -DistroName $firstDistro | |
if ($intermediateCertPath) { | |
$exportedCerts += $intermediateCertPath | |
} | |
} | |
} | |
# Create certificate bundle from the exported certificates | |
$bundlePath = $null | |
if ($CreateBundle) { | |
$bundlePath = New-CertificateBundle -CertificateDirectory $CertificateDirectory -BundleFileName "Corporate_CA_Bundle.crt" -CertificateFiles $exportedCerts | |
} | |
# Determine target WSL distributions | |
$targetDistributions = @() | |
$installationSummary = @() | |
if (-not $SkipWSLInstall) { | |
$allDistributions = Get-WSLDistributionsFromRegistry | |
if ($InstallAllDistros) { | |
Write-Status "Installing certificates for all $($allDistributions.Count) WSL distributions" "INFO" | |
$targetDistributions = $allDistributions | |
} else { | |
# Single distribution mode | |
if (-not $WSLDistro) { | |
$WSLDistro = Get-DefaultWSLDistro | |
if (-not $WSLDistro) { | |
Write-Status "Could not determine WSL distribution. Please specify -WSLDistro parameter." "ERROR" | |
return | |
} | |
} | |
$targetDistro = $allDistributions | Where-Object { $_.Name -eq $WSLDistro } | |
if (-not $targetDistro) { | |
Write-Status "WSL distribution '$WSLDistro' not found" "ERROR" | |
Write-Status "Available distributions: $($allDistributions.Name -join ', ')" "INFO" | |
return | |
} | |
$targetDistributions = @($targetDistro) | |
} | |
Write-Status "Target WSL distributions: $($targetDistributions.Name -join ', ')" "INFO" | |
# Process each WSL distribution | |
foreach ($distro in $targetDistributions) { | |
Write-Status "" | |
Write-Status "=======================================================" "INFO" | |
Write-Status "Processing: $($distro.Name) ($($distro.DistributionFamily) family)" "INFO" | |
Write-Status "=======================================================" "INFO" | |
# Test distribution availability | |
if (-not (Test-WSLDistroAvailability -DistroName $distro.Name)) { | |
Write-Status "Distribution '$($distro.Name)' is not responsive, skipping..." "ERROR" | |
$installationSummary += @{ | |
Distribution = $distro.Name | |
Success = $false | |
Reason = "Distribution not responsive" | |
} | |
continue | |
} | |
# Get distribution configuration | |
$distroConfig = $null | |
if ($CustomWSLPaths.ContainsKey($distro.Name)) { | |
$distroConfig = $CustomWSLPaths[$distro.Name] | |
Write-Status "Using custom configuration for $($distro.Name)" "INFO" | |
} elseif ($WSLDistroConfigs.ContainsKey($distro.Name)) { | |
$distroConfig = $WSLDistroConfigs[$distro.Name] | |
Write-Status "Using predefined configuration for $($distro.Name)" "SUCCESS" | |
} else { | |
# Use family-based defaults | |
$family = $distro.DistributionFamily | |
$distroConfig = switch ($family) { | |
"Debian" { $WSLDistroConfigs["Ubuntu"] } | |
"RHEL" { $WSLDistroConfigs["FedoraLinux-42"] } | |
"SUSE" { $WSLDistroConfigs["openSUSE-Tumbleweed"] } | |
"Arch" { $WSLDistroConfigs["archlinux"] } | |
default { $WSLDistroConfigs["Ubuntu"] } | |
} | |
Write-Status "Using $family family defaults for $($distro.Name)" "INFO" | |
} | |
# Install dependencies | |
if (-not (Install-WSLDependencies -DistroName $distro.Name)) { | |
Write-Status "Failed to install dependencies in $($distro.Name), skipping..." "ERROR" | |
$installationSummary += @{ | |
Distribution = $distro.Name | |
Success = $false | |
Reason = "Failed to install dependencies" | |
} | |
continue | |
} | |
# Test connectivity before installation | |
$connectivityBefore = $null | |
if ($TestBeforeAndAfter) { | |
$connectivityBefore = Test-ConnectivityForDistribution -DistroName $distro.Name -TestUrls $TestUrls -TestPhase "BEFORE" | |
} | |
# Install certificates | |
Write-Status "" | |
Write-Status "Installing certificates in $($distro.Name)..." "INFO" | |
# Remove old certificates | |
Remove-OldCertificatesFromWSL -DistroName $distro.Name -CertPath $distroConfig.CertPath | Out-Null | |
$successfulInstalls = 0 | |
foreach ($certPath in $exportedCerts) { | |
if (Install-CertificateInWSL -CertFilePath $certPath -DistroName $distro.Name -DistroConfig $distroConfig) { | |
$successfulInstalls++ | |
} | |
} | |
# Update certificate store | |
$storeUpdateSuccess = $false | |
if ($successfulInstalls -gt 0) { | |
$storeUpdateSuccess = Update-WSLCertificateStore -DistroName $distro.Name -DistroConfig $distroConfig | |
} | |
# Test connectivity after installation | |
$connectivityAfter = $null | |
if ($TestBeforeAndAfter -and $storeUpdateSuccess) { | |
Write-Status "" | |
$connectivityAfter = Test-ConnectivityForDistribution -DistroName $distro.Name -TestUrls $TestUrls -TestPhase "AFTER" | |
} | |
# Setup WSL environment variables | |
if ($SetupWSLEnvironment -and $bundlePath -and $storeUpdateSuccess) { | |
Set-WSLEnvironmentVariables -DistroName $distro.Name -BundlePath $bundlePath | Out-Null | |
} | |
# Record results | |
$improvement = if ($connectivityBefore -and $connectivityAfter) { | |
$connectivityAfter.SuccessRate - $connectivityBefore.SuccessRate | |
} else { 0 } | |
$installationSummary += @{ | |
Distribution = $distro.Name | |
Family = $distro.DistributionFamily | |
Success = $storeUpdateSuccess | |
Reason = if ($storeUpdateSuccess) { "Success" } else { "Certificate store update failed" } | |
CertificatesInstalled = $successfulInstalls | |
ConnectivityBefore = $connectivityBefore | |
ConnectivityAfter = $connectivityAfter | |
Improvement = $improvement | |
} | |
} | |
} | |
# Configure environment variables | |
if ($SetupNodeJS -and $bundlePath) { | |
Write-Status "" | |
Set-NodeJSEnvironmentVariables -BundlePath $bundlePath | Out-Null | |
} | |
# Final comprehensive summary | |
Write-Status "" | |
Write-Status "===================================================================" | |
Write-Status " INSTALLATION SUMMARY" | |
Write-Status "===================================================================" | |
Write-Status "Certificates found: $($certificateMatches.Count)" | |
Write-Status "Certificates exported: $($exportedCerts.Count)" | |
Write-Status "Certificate directory: $CertificateDirectory" | |
if ($bundlePath) { | |
Write-Status "Certificate bundle: $(Split-Path -Leaf $bundlePath)" | |
} | |
# WSL Installation Summary | |
if (-not $SkipWSLInstall -and $installationSummary.Count -gt 0) { | |
Write-Status "" | |
Write-Status "WSL Installation Results:" "INFO" | |
Write-Status "========================" "INFO" | |
$successfulDistros = ($installationSummary | Where-Object { $_.Success }).Count | |
$totalCertsInstalled = 0 | |
foreach ($summary in $installationSummary) { | |
if ($summary.CertificatesInstalled) { | |
$totalCertsInstalled += $summary.CertificatesInstalled | |
} | |
} | |
foreach ($summary in $installationSummary) { | |
$status = if ($summary.Success) { "[SUCCESS]" } else { "[FAILED]" } | |
Write-Status "$status $($summary.Distribution) ($($summary.Family))" $(if ($summary.Success) { "SUCCESS" } else { "ERROR" }) | |
Write-Status " Certificates installed: $($summary.CertificatesInstalled)" "INFO" | |
if ($summary.ConnectivityBefore -and $summary.ConnectivityAfter) { | |
$before = $summary.ConnectivityBefore.SuccessRate | |
$after = $summary.ConnectivityAfter.SuccessRate | |
$change = $after - $before | |
$changeStr = if ($change -gt 0) { "+$change" } elseif ($change -lt 0) { "$change" } else { "0" } | |
Write-Status " Connectivity: $before% -> $after% ($changeStr%)" "INFO" | |
} | |
if (-not $summary.Success) { | |
Write-Status " Reason: $($summary.Reason)" "ERROR" | |
} | |
} | |
Write-Status "" | |
Write-Status "Overall: $successfulDistros/$($installationSummary.Count) distributions successful" $(if ($successfulDistros -eq $installationSummary.Count) { "SUCCESS" } else { "WARNING" }) | |
Write-Status "Total certificates installed: $totalCertsInstalled" "INFO" | |
} | |
# Environment Variables Summary | |
if ($SetupNodeJS -or $SetupEnvironmentVars -or $SetupWSLEnvironment) { | |
Write-Status "" | |
Write-Status "Environment Configuration:" "INFO" | |
if ($SetupNodeJS -or $SetupEnvironmentVars) { | |
Write-Status "Windows environment: Configured" "SUCCESS" | |
} | |
$wslEnvDistros = ($installationSummary | Where-Object { $_.Success -and $SetupWSLEnvironment }).Count | |
if ($wslEnvDistros -gt 0) { | |
Write-Status "WSL environment: Configured for $wslEnvDistros distribution(s)" "SUCCESS" | |
} | |
} | |
# Certificate Files Created | |
Write-Status "" | |
Write-Status "Certificate files created:" "INFO" | |
Get-ChildItem -Path $CertificateDirectory -Filter "*.crt" | ForEach-Object { | |
$size = [Math]::Round($_.Length / 1KB, 1) | |
Write-Host " - $($_.Name) ($size KB)" -ForegroundColor Gray | |
} | |
# Final Status | |
Write-Status "" | |
$overallSuccess = if ($SkipWSLInstall) { | |
$exportedCerts.Count -gt 0 | |
} else { | |
($installationSummary | Where-Object { $_.Success }).Count -gt 0 | |
} | |
if ($overallSuccess) { | |
Write-Status "Installation completed successfully!" "SUCCESS" | |
if (-not $SkipWSLInstall) { | |
Write-Status "Corporate certificates are now available in WSL distributions" "SUCCESS" | |
} else { | |
Write-Status "Certificate export completed successfully!" "SUCCESS" | |
} | |
if ($SetupNodeJS -or $SetupEnvironmentVars) { | |
Write-Status "" | |
Write-Status "IMPORTANT: Restart your terminal/IDE to apply Windows environment changes" "WARNING" | |
} | |
if ($SetupWSLEnvironment) { | |
Write-Status "IMPORTANT: Start new WSL sessions to apply WSL environment variables" "WARNING" | |
} | |
} else { | |
Write-Status "Installation completed with issues" "WARNING" | |
if (-not $SkipWSLInstall) { | |
Write-Status "Some distributions may not have been configured correctly" "WARNING" | |
} | |
} | |
# Manual test commands | |
Write-Status "" | |
Write-Status "Manual test commands:" "INFO" | |
if (-not $SkipWSLInstall -and $installationSummary.Count -gt 0) { | |
$firstSuccessful = ($installationSummary | Where-Object { $_.Success })[0] | |
if ($firstSuccessful) { | |
Write-Host " wsl -d $($firstSuccessful.Distribution) curl -v https://google.com" -ForegroundColor Gray | |
Write-Host " wsl -d $($firstSuccessful.Distribution) curl -v https://github.com" -ForegroundColor Gray | |
} | |
} | |
# Log file information | |
if ($script:LogFilePath) { | |
Write-Status "" | |
Write-Status "Detailed log saved to: $script:LogFilePath" "INFO" | |
} | |
Write-Status "" | |
Write-Status "Completed: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" | |
Write-Status "===================================================================" | |
} | |
#endregion | |
# Execute main function if script is run directly (not dot-sourced) | |
if ($MyInvocation.InvocationName -ne '.') { | |
Invoke-Main | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@JustACasual I was able to work on this one. Here is public repo based on this script and a lot of verified features based on my latest experience
https://github.com/emilwojcik93/corporate-ssl-manager