Skip to content

Instantly share code, notes, and snippets.

@emilwojcik93
Last active September 4, 2025 01:06
Show Gist options
  • Save emilwojcik93/7eb1e172f8bb038e324c6e4a7f4ccaaa to your computer and use it in GitHub Desktop.
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.
#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
}
@emilwojcik93
Copy link
Author

emilwojcik93 commented Feb 16, 2025

Auto-Install-CertificatesInWSL.ps1

This script searches for certificates with a specific description pattern, exports them, installs them in WSL, and checks the response using curl.

Usage

.\Auto-Install-CertificatesInWSL.ps1 [options]

Options

  • -DescriptionPattern <String>: The pattern to search for in the certificate description. Default is "CA".
  • -ExcludeIssuers <String[]>: An array of issuer names to exclude from the results. Default is an array of common built-in certificate issuers.
  • -WSLDistro <String>: The WSL distribution to install the certificate in. Default is "Ubuntu".
  • -UpdateCommand <String>: The command to update the CA certificates. Default is the value from the $wslDistros array.
  • -Verbose: Enables verbose output.

Running the Script from the Internet:

Use Invoke-RestMethod to download and execute the script. Here is how you can do it:

# Using Invoke-RestMethod
irm https://gist.githubusercontent.com/emilwojcik93/7eb1e172f8bb038e324c6e4a7f4ccaaa/raw/Auto-Install-CertificatesInWSL.ps1 | iex

Note

If it doesn't work, then try to Set-ExecutionPolicy via PowerShell (Admin)

Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force; irm https://gist.githubusercontent.com/emilwojcik93/7eb1e172f8bb038e324c6e4a7f4ccaaa/raw/Auto-Install-CertificatesInWSL.ps1 | iex

Note

To execute the script from the Internet with additional parameters, please run

&([ScriptBlock]::Create((irm https://gist.githubusercontent.com/emilwojcik93/7eb1e172f8bb038e324c6e4a7f4ccaaa/raw/Auto-Install-CertificatesInWSL.ps1))) -Verbose

Example of issue

$ curl https://ipchicken.com/
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

Example of execution

PS > &([ScriptBlock]::Create((irm https://gist.githubusercontent.com/emilwojcik93/7eb1e172f8bb038e324c6e4a7f4ccaaa/raw/Auto-Install-CertificatesInWSL.ps1))) -Verbose
VERBOSE: Searching certificates in store: Cert:\LocalMachine\My
VERBOSE: Searching certificates in store: Cert:\LocalMachine\Root
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::LocalMachine\Root\<THUMBPRINT_VALUE_1>
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::LocalMachine\Root\<THUMBPRINT_VALUE_2>
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::LocalMachine\Root\<THUMBPRINT_VALUE_3>
VERBOSE: Searching certificates in store: Cert:\LocalMachine\CA
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::LocalMachine\CA\<THUMBPRINT_VALUE_4>
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::LocalMachine\CA\<THUMBPRINT_VALUE_5>
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::LocalMachine\CA\<THUMBPRINT_VALUE_6>
VERBOSE: Searching certificates in store: Cert:\LocalMachine\AuthRoot
VERBOSE: Searching certificates in store: Cert:\LocalMachine\TrustedPublisher
VERBOSE: Searching certificates in store: Cert:\LocalMachine\TrustedPeople
VERBOSE: Searching certificates in store: Cert:\LocalMachine\Disallowed
VERBOSE: Searching certificates in store: Cert:\CurrentUser\My
VERBOSE: Searching certificates in store: Cert:\CurrentUser\Root
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::CurrentUser\Root\<THUMBPRINT_VALUE_1>
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::CurrentUser\Root\<THUMBPRINT_VALUE_2>
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::CurrentUser\Root\<THUMBPRINT_VALUE_3>
VERBOSE: Searching certificates in store: Cert:\CurrentUser\CA
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::CurrentUser\CA\<THUMBPRINT_VALUE_4>
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::CurrentUser\CA\<THUMBPRINT_VALUE_5>
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::CurrentUser\CA\<THUMBPRINT_VALUE_6>
VERBOSE: Searching certificates in store: Cert:\CurrentUser\AuthRoot
VERBOSE: Searching certificates in store: Cert:\CurrentUser\TrustedPublisher
VERBOSE: Searching certificates in store: Cert:\CurrentUser\TrustedPeople
VERBOSE: Searching certificates in store: Cert:\CurrentUser\Disallowed
VERBOSE: Exporting certificate to file: C:\Users\YourUser\AppData\Local\Temp\Cert_Root_CA.crt
VERBOSE: 
VERBOSE: './Cert_Root_CA.crt' -> '/usr/local/share/ca-certificates/Cert_Root_CA.crt'
VERBOSE: File exists
Certificate copied to /usr/local/share/ca-certificates/Cert_Root_CA.crt
VERBOSE: Command check output: true
Success: update-ca-certificates is available in WSL.
VERBOSE: Updating certificates in /etc/ssl/certs...
0 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.
CA certificates updated in WSL.
VERBOSE: Response from Test-CertificateInWSL for https://www.whatismyip.com/: 000
WARNING: Certificate did not pass verification for https://www.whatismyip.com/.
VERBOSE: Response from Test-CertificateInWSL for https://www.showmyip.com/: 000
WARNING: Certificate did not pass verification for https://www.showmyip.com/.
VERBOSE: Response from Test-CertificateInWSL for https://ipchicken.com/: 000
WARNING: Certificate did not pass verification for https://ipchicken.com/.
VERBOSE: Response from Test-CertificateInWSL for https://ifconfig.co/: 000
WARNING: Certificate did not pass verification for https://ifconfig.co/.
VERBOSE: Response from Test-CertificateInWSL for https://google.com/: 000
WARNING: Certificate did not pass verification for https://google.com/.
WARNING: Certificate verification failed for all addresses. Trying next certificate.
VERBOSE: 
VERBOSE: Exporting certificate to file: C:\Users\YourUser\AppData\Local\Temp\Cert_Root_CA3.crt
VERBOSE: 
VERBOSE: './Cert_Root_CA3.crt' -> '/usr/local/share/ca-certificates/Cert_Root_CA3.crt'
VERBOSE: File exists
Certificate copied to /usr/local/share/ca-certificates/Cert_Root_CA3.crt
VERBOSE: Command check output: true
Success: update-ca-certificates is available in WSL.
rehash: warning: skipping ca-certificates.crt,it does not contain exactly one certificate or CRL
VERBOSE: Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.
CA certificates updated in WSL.
VERBOSE: Response from Test-CertificateInWSL for https://www.whatismyip.com/: 000
WARNING: Certificate did not pass verification for https://www.whatismyip.com/.
VERBOSE: Response from Test-CertificateInWSL for https://www.showmyip.com/: 000
WARNING: Certificate did not pass verification for https://www.showmyip.com/.
VERBOSE: Response from Test-CertificateInWSL for https://ipchicken.com/: 000
WARNING: Certificate did not pass verification for https://ipchicken.com/.
VERBOSE: Response from Test-CertificateInWSL for https://ifconfig.co/: 000
WARNING: Certificate did not pass verification for https://ifconfig.co/.
VERBOSE: Response from Test-CertificateInWSL for https://google.com/: 000
WARNING: Certificate did not pass verification for https://google.com/.
WARNING: Certificate verification failed for all addresses. Trying next certificate.
VERBOSE: 
VERBOSE: Exporting certificate to file: C:\Users\YourUser\AppData\Local\Temp\Cert_Root_CA1.crt
VERBOSE: 
VERBOSE: './Cert_Root_CA1.crt' -> '/usr/local/share/ca-certificates/Cert_Root_CA1.crt'
VERBOSE: File exists
Certificate copied to /usr/local/share/ca-certificates/Cert_Root_CA1.crt
VERBOSE: Command check output: true
Success: update-ca-certificates is available in WSL.
rehash: warning: skipping ca-certificates.crt,it does not contain exactly one certificate or CRL
VERBOSE: Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.
CA certificates updated in WSL.
VERBOSE: Response from Test-CertificateInWSL for https://www.whatismyip.com/: 000
WARNING: Certificate did not pass verification for https://www.whatismyip.com/.
VERBOSE: Response from Test-CertificateInWSL for https://www.showmyip.com/: 000
WARNING: Certificate did not pass verification for https://www.showmyip.com/.
VERBOSE: Response from Test-CertificateInWSL for https://ipchicken.com/: 000
WARNING: Certificate did not pass verification for https://ipchicken.com/.
VERBOSE: Response from Test-CertificateInWSL for https://ifconfig.co/: 000
WARNING: Certificate did not pass verification for https://ifconfig.co/.
VERBOSE: Response from Test-CertificateInWSL for https://google.com/: 000
WARNING: Certificate did not pass verification for https://google.com/.
WARNING: Certificate verification failed for all addresses. Trying next certificate.
VERBOSE: 
VERBOSE: Exporting certificate to file: C:\Users\YourUser\AppData\Local\Temp\Cert_Root_CA.crt
VERBOSE: 
VERBOSE: './Cert_Root_CA.crt' -> '/usr/local/share/ca-certificates/Cert_Root_CA.crt'
VERBOSE: File exists
Certificate copied to /usr/local/share/ca-certificates/Cert_Root_CA.crt
VERBOSE: Command check output: true
Success: update-ca-certificates is available in WSL.
rehash: warning: skipping ca-certificates.crt,it does not contain exactly one certificate or CRL
VERBOSE: Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.
CA certificates updated in WSL.
VERBOSE: Response from Test-CertificateInWSL for https://www.whatismyip.com/: 403
WARNING: Certificate did not pass verification for https://www.whatismyip.com/.
VERBOSE: Response from Test-CertificateInWSL for https://www.showmyip.com/: 200
Certificate installed and verified successfully for https://www.showmyip.com/.
Certificate installed and verified successfully for at least one address.

Example of execution for ArchLinux

> & ".\Auto-Install-CertificatesInWSL.ps1" -WSLDistro Arch -Verbose
VERBOSE: Searching certificates in store: Cert:\LocalMachine\My
VERBOSE: Searching certificates in store: Cert:\LocalMachine\Root
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::LocalMachine\Root\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::LocalMachine\Root\BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::LocalMachine\Root\CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
VERBOSE: Searching certificates in store: Cert:\LocalMachine\CA
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::LocalMachine\CA\DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::LocalMachine\CA\EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::LocalMachine\CA\FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
VERBOSE: Searching certificates in store: Cert:\LocalMachine\AuthRoot
VERBOSE: Searching certificates in store: Cert:\LocalMachine\TrustedPublisher
VERBOSE: Searching certificates in store: Cert:\LocalMachine\TrustedPeople
VERBOSE: Searching certificates in store: Cert:\LocalMachine\Disallowed
VERBOSE: Searching certificates in store: Cert:\CurrentUser\My
VERBOSE: Searching certificates in store: Cert:\CurrentUser\Root
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::CurrentUser\Root\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::CurrentUser\Root\BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::CurrentUser\Root\CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
VERBOSE: Searching certificates in store: Cert:\CurrentUser\CA
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::CurrentUser\CA\DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::CurrentUser\CA\EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
VERBOSE: Matched certificate: Microsoft.PowerShell.Security\Certificate::CurrentUser\CA\FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
VERBOSE: Searching certificates in store: Cert:\CurrentUser\AuthRoot
VERBOSE: Searching certificates in store: Cert:\CurrentUser\TrustedPublisher
VERBOSE: Searching certificates in store: Cert:\CurrentUser\TrustedPeople
VERBOSE: Searching certificates in store: Cert:\CurrentUser\Disallowed
VERBOSE: Exporting certificate to file: C:\Users\username\AppData\Local\Temp\Example_Root_CA.crt
VERBOSE: 
VERBOSE: './Example_Root_CA.crt' -> '/etc/ca-certificates/trust-source/anchors/Example_Root_CA.crt'
VERBOSE: File exists
Certificate copied to /etc/ca-certificates/trust-source/anchors/Example_Root_CA.crt
VERBOSE: Command check output: true
Success: trust extract-compat is available in WSL.
VERBOSE: 
CA certificates updated in WSL.
VERBOSE: Response from Test-CertificateInWSL for https://www.example.com/: 403
WARNING: Certificate did not pass verification for https://www.example.com/.
VERBOSE: Response from Test-CertificateInWSL for https://www.example2.com/: 200

Functions

Get-DefaultWSLDistro

  • Purpose: Retrieves the default WSL distribution from the registry.
  • Example: $defaultDistro = Get-DefaultWSLDistro

Test-WSL

  • Purpose: Checks if WSL is available and if any distributions are installed.
  • Example: $isWSLAvailable = Test-WSL

Test-CurlAvailability

  • Purpose: Checks if curl is available in the target WSL distribution.
  • Parameters:
    • WSLDistro: The WSL distribution to check.
  • Example: $isCurlAvailable = Test-CurlAvailability -WSLDistro "Ubuntu"

Search-Certificates

  • Purpose: Searches for certificates with a specific description pattern.
  • Parameters:
    • DescriptionPattern: The pattern to search for in the certificate description.
    • ExcludeIssuers: An array of issuer names to exclude from the results.
  • Example: $certificates = Search-Certificates -DescriptionPattern "CA" -ExcludeIssuers @("DigiCert", "thawte")

Export-CertificateToFile

  • Purpose: Exports a certificate to a file in Base64 format.
  • Parameters:
    • Cert: The certificate to export.
    • FilePath: The file path to export the certificate to.
  • Example: Export-CertificateToFile -Cert $cert -FilePath "C:\Temp\cert.crt"

Remove-OldCertificateFromWSL

  • Purpose: Removes an old certificate from WSL.
  • Parameters:
    • CertFileName: The name of the certificate file to remove.
    • WSLDistro: The WSL distribution to remove the certificate from.
  • Example: Remove-OldCertificateFromWSL -CertFileName "cert.crt" -WSLDistro "Ubuntu"

Install-CertificateInWSL

  • Purpose: Installs a certificate in WSL.
  • Parameters:
    • CertFilePath: The file path of the certificate to install.
    • WSLDistro: The WSL distribution to install the certificate in.
    • UpdateCommand: The command to update the CA certificates.
    • CertPath: The path to install the certificate in WSL.
  • Example: Install-CertificateInWSL -CertFilePath "C:\Temp\cert.crt" -WSLDistro "Ubuntu" -UpdateCommand "update-ca-certificates" -CertPath "/usr/local/share/ca-certificates/"

Test-CertificateInWSL

  • Purpose: Checks the response from WSL using curl.
  • Parameters:
    • WSLDistro: The WSL distribution to check the certificate in.
    • Address: The address to test the certificate against.
  • Example: $response = Test-CertificateInWSL -WSLDistro "Ubuntu" -Address "https://google.com"

Main

  • Purpose: Main function to orchestrate the certificate search, export, installation, and verification.
  • Parameters:
    • DescriptionPattern: The pattern to search for in the certificate description.
    • ExcludeIssuers: An array of issuer names to exclude from the results.
    • WSLDistro: The WSL distribution to install the certificate in.
    • UpdateCommand: The command to update the CA certificates.
    • Verbose: Enables verbose output.
  • Example: Main -DescriptionPattern "CA" -ExcludeIssuers @("DigiCert", "thawte") -WSLDistro "Ubuntu" -Verbose

Related Links

@JosephLofving
Copy link

Absolutely amazing. Thank you! Spent hours trying to get my certs to work, and this worked beautifully.

@JustACasual
Copy link

Hi @emilwojcik93, thank you so so much for this script.
It helped me solve my own problem. Would you be interested in turning this into a small Repo to allow for public contributions and issues?

Otherwise, I could also do it and link here, if you don't mind.

@emilwojcik93
Copy link
Author

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment