This guide provides detailed technical information for developers who want to contribute to or understand the internal workings of pxp-cli.
- Architecture Overview
- Core Components
- Directory Structure
- How It Works
- Key Functions
- Configuration Management
- Apache Integration
- Error Handling
- Building & Distribution
- Testing Guide
- Debugging Tips
pxp-cli is a PowerShell-based CLI tool wrapped in a batch file for easy execution. It manages multiple PHP versions in XAMPP by manipulating directories and Apache configuration files.
- Safety First: Always backup before making changes
- User-Friendly: Clear error messages and helpful guidance
- Zero Dependencies: Works with vanilla Windows + XAMPP
- Non-Invasive: Doesn't modify core XAMPP files permanently
User Command
↓
pxp.bat (wrapper)
↓
pxp.ps1 (main script)
↓
┌─────────────────────────────────────┐
│ Parse command & arguments │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Validate XAMPP installation │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Execute command logic │
│ - Switch, Install, List, etc. │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Update Apache configuration │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Restart Apache (if needed) │
└─────────────────────────────────────┘
↓
Result Output
Purpose: Wrapper script to invoke PowerShell with correct execution policy.
@echo off
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0pxp.ps1" %*- -NoProfile: Prevents user PowerShell profile from interfering
- -ExecutionPolicy Bypass: Allows script execution regardless of system policy
- %~dp0: Expands to the directory containing the batch file
- %*: Passes all arguments to PowerShell script
Purpose: Main PowerShell script containing all logic.
Structure:
# Parameters definition
param(...)
# Configuration variables
$PXP_VERSION = "1.3.1"
$XAMPP_ROOT = "C:\xampp"
...
# Helper functions
function Get-PhpVersion { ... }
function Get-PhpMajor { ... }
...
# Command handlers
switch ($Command) {
"list" { ... }
"switch" { ... }
"install" { ... }
"uninstall" { ... }
...
}Location: installer/Pxp.wixproj and installer/Pxp.wxs
Purpose: Creates MSI installer for easy distribution.
Key Features:
- Installs scripts to
Program Files (x86)\Pxp - Adds installation directory to system PATH
- Creates uninstaller entry in Windows
- Supports upgrade/downgrade scenarios
XAMPP stores PHP in C:\xampp\php\. pxp manages multiple versions by:
-
Renaming directories to store versions:
- Active:
C:\xampp\php\ - Stored:
C:\xampp\php7.4.33\,C:\xampp\php8.0.30\, etc.
- Active:
-
When switching:
- Backup current
php/→php{version}/ - Rename target
php{version}/→php/ - Update Apache config
- Restart Apache
- Backup current
From directory name:
# Extract version from directory name: "php8.0.30" → "8.0.30"
$version = $dir.Name -replace "^php", ""From php.exe:
function Get-PhpVersion ([string]$PhpDir) {
$exe = Join-Path $PhpDir "php.exe"
if (Test-Path $exe) {
return [System.Diagnostics.FileVersionInfo]::GetVersionInfo($exe).FileVersion
}
return $null
}PHP major version (for Apache module):
function Get-PhpMajor ([string]$PhpDir) {
if (Test-Path (Join-Path $PhpDir "php7apache2_4.dll")) { return 7 }
if (Test-Path (Join-Path $PhpDir "php8apache2_4.dll")) { return 8 }
return $null
}Purpose: Extract PHP version from php.exe file metadata.
function Get-PhpVersion ([string]$PhpDir) {
$exe = Join-Path $PhpDir "php.exe"
if (Test-Path $exe) {
return [System.Diagnostics.FileVersionInfo]::GetVersionInfo($exe).FileVersion
}
return $null
}Purpose: Determine PHP major version (7 or 8) by checking for Apache DLL.
function Get-PhpMajor ([string]$PhpDir) {
if (Test-Path (Join-Path $PhpDir "php7apache2_4.dll")) { return 7 }
if (Test-Path (Join-Path $PhpDir "php8apache2_4.dll")) { return 8 }
return $null
}Purpose: Get correct Apache module name based on PHP major version.
function Get-ApacheModuleName ([int]$Major) {
switch ($Major) {
7 { return "php7_module" } # For PHP 7.x
8 { return "php_module" } # For PHP 8.x
default { return "php_module" }
}
}XAMPP Convention:
- PHP 7.x →
php7_module(loadsphp7apache2_4.dll) - PHP 8.x →
php_module(loadsphp8apache2_4.dll)
Purpose: Control Apache service before and after PHP switches.
function Stop-ApacheService {
if (Test-Path $APACHE_BIN) {
Write-Host "Stopping Apache..." -ForegroundColor Yellow
& $APACHE_BIN -k stop 2>&1 | Out-Null
Start-Sleep -Seconds 2
}
}
function Start-ApacheService {
if (Test-Path $APACHE_BIN) {
Write-Host "Starting Apache..." -ForegroundColor Yellow
& $APACHE_BIN -k start 2>&1 | Out-Null
Start-Sleep -Seconds 2
Write-Host "Apache started successfully!" -ForegroundColor Green
}
}Location: C:\xampp\apache\conf\extra\httpd-xampp.conf
Key sections modified:
# LoadModule directive - different for PHP 7 vs 8
LoadModule php7_module "C:/xampp/php/php7apache2_4.dll" # PHP 7.x
# or
LoadModule php_module "C:/xampp/php/php8apache2_4.dll" # PHP 8.xFirst-time backup:
$backupFile = "$APACHE_CONF.bak"
if (-not (Test-Path $backupFile)) {
Copy-Item $APACHE_CONF $backupFile
Write-Host "Created backup: $backupFile"
}Before each switch:
- Current
php/directory backed up tophp{version}/ - Configuration updates applied
- If something fails, original can be restored
-
Read current config:
$content = Get-Content $APACHE_CONF -Raw
-
Replace LoadModule directive:
$pattern = 'LoadModule\s+(php7?_module)\s+"[^"]*"' $replacement = "LoadModule $moduleName `"C:/xampp/php/$dllName`"" $content = $content -replace $pattern, $replacement
-
Write updated config:
Set-Content -Path $APACHE_CONF -Value $content -NoNewline
-
Restart Apache:
Stop-ApacheService Start-ApacheService
# Stop Apache
& "C:\xampp\apache\bin\httpd.exe" -k stop
# Start Apache
& "C:\xampp\apache\bin\httpd.exe" -k start
# Restart Apache
& "C:\xampp\apache\bin\httpd.exe" -k restart-
XAMPP not installed:
if (-not (Test-Path $XAMPP_ROOT)) { Write-Host "Error: XAMPP not found at $XAMPP_ROOT" -ForegroundColor Red exit 1 }
-
PHP version not found:
if (-not (Test-Path $targetPhp)) { Write-Host "Error: PHP $version not found" -ForegroundColor Red Write-Host "Available versions:" -ForegroundColor Yellow Show-InstalledVersions exit 1 }
-
Permission denied:
try { Rename-Item $phpDir $targetDir -ErrorAction Stop } catch { Write-Host "Error: Permission denied. Run as Administrator." -ForegroundColor Red exit 1 }
-
Apache not responding:
$maxRetries = 3 for ($i = 0; $i -lt $maxRetries; $i++) { & $APACHE_BIN -k start Start-Sleep -Seconds 2 if (Test-ApacheRunning) { break } }
Prerequisites:
# Install .NET SDK 6.0+
# Install WiX toolset as .NET tool
dotnet tool restoreBuild command:
dotnet build ./installer/Pxp.wixproj -c ReleaseWith custom version:
dotnet build ./installer/Pxp.wixproj -c Release -p:ProductVersion=1.3.0Output:
- Location:
installer\bin\Release\ - File:
pxp-v{version}.msi
-
Update version in
pxp.ps1:$PXP_VERSION = "1.3.1"
-
Update CHANGELOG.md with release notes
-
Build installer:
dotnet build ./installer/Pxp.wixproj -c Release -p:ProductVersion=1.3.1
-
Create Git tag:
git tag v1.3.1 git push origin v1.3.1
-
GitHub Actions automatically creates release and uploads MSI
Currently, the project uses manual testing. Future improvements could include:
# Example test structure (Pester framework)
Describe "Get-PhpVersion" {
It "Returns version from valid PHP directory" {
$version = Get-PhpVersion "C:\xampp\php"
$version | Should -Match "\d+\.\d+\.\d+"
}
It "Returns null for invalid directory" {
$version = Get-PhpVersion "C:\invalid"
$version | Should -BeNullOrEmpty
}
}Test scenarios:
-
Switch between versions:
pxp switch 7.4.33 # Verify: php -v shows 7.4.33 # Verify: Apache is running pxp switch 8.0.30 # Verify: php -v shows 8.0.30 # Verify: Apache is running
-
Install new version:
pxp install 8.3.15 # Verify: php8.3.15 directory exists # Verify: Files downloaded correctly pxp switch 8.3.15 # Verify: Switch successful
-
List versions:
pxp list # Verify: All installed versions shown # Verify: Current version marked with *
- Commands work in PowerShell 5.1
- Commands work in PowerShell 7+
- Commands work via batch file
- Works on Windows 10
- Works on Windows 11
- Admin rights handled correctly
- Error messages are clear
- Apache restarts successfully
- No data loss during operations
- Installer works correctly
Add debug output to functions:
function Debug-Log ([string]$Message) {
if ($DebugPreference -eq "Continue") {
Write-Debug $Message
}
}
# Usage
Debug-Log "Switching from PHP $currentVersion to $targetVersion"# Check PHP version
& "C:\xampp\php\php.exe" -v
# Check Apache configuration
& "C:\xampp\apache\bin\httpd.exe" -t
# View Apache error log
Get-Content "C:\xampp\apache\logs\error.log" -Tail 50
# Check Apache status
Get-Process httpd -ErrorAction SilentlyContinue
# Test Apache config syntax
& "C:\xampp\apache\bin\httpd.exe" -t# Enable script debugging
Set-PSDebug -Trace 1
# Disable script debugging
Set-PSDebug -Off
# Set breakpoint
Set-PSBreakpoint -Script .\pxp.ps1 -Line 100
# Step through code
$DebugPreference = "Continue"# Apache error log
Get-Content "C:\xampp\apache\logs\error.log" | Select-String "PHP"
# Apache access log
Get-Content "C:\xampp\apache\logs\access.log" | Select-String "error" -Context 2- Minimize file operations: Batch file operations when possible
- Cache version information: Avoid repeated file system calls
- Parallel downloads: Use background jobs for downloads (future enhancement)
- Lazy loading: Only load necessary modules
- Apache restart: Takes 2-5 seconds
- Directory rename: Usually fast, but can be slow on slow disks
- Download speed: Depends on network and PHP.net servers
-
PHP Extension Management:
- Enable/disable extensions via CLI
- List available extensions
-
Configuration Profiles:
- Save/load php.ini configurations
- Quick switch between development/production
-
Multi-XAMPP Support:
- Support multiple XAMPP installations
- Custom installation paths
-
Backup Management:
- List all backups
- Restore from backup
- Clean old backups
-
Web Interface:
- Simple local web UI for management
- Visual representation of versions
See CONTRIBUTING.md for detailed contribution guidelines.
- Fork the repository
- Clone your fork
- Make changes in a feature branch
- Test thoroughly
- Submit a pull request
- Visual Studio Code - Recommended editor
- PowerShell Extension for VS Code
- WiX Toolset
For questions or issues:
- Check TROUBLESHOOTING.md
- Search existing issues
- Create a new issue if needed
Happy coding! 🚀