From c75a8eb9331cefaa3d6707c9bf8aa70c99ea6502 Mon Sep 17 00:00:00 2001 From: Thomas Powell Date: Mon, 27 Oct 2025 16:49:45 -0400 Subject: [PATCH] Test DLLs Signed-off-by: Thomas Powell --- .dockerignore | 2 + .gitignore | 2 + Dockerfile | 40 +- Dockerfile.msi | 53 ++ README.md | 125 +++- advanced-dll-analysis.ps1 | 229 ++++++++ analyze-dll-changes-fixed.ps1 | 321 ++++++++++ analyze-dll-changes-new.ps1 | 321 ++++++++++ analyze-dll-changes.ps1 | 321 ++++++++++ compare-dlls.ps1 | 331 +++++++++++ compare-gem-install-fixed.ps1 | 332 +++++++++++ compare-gem-install.ps1 | 335 +++++++++++ compare-gem-pristine.ps1 | 417 +++++++++++++ cookbooks/test_recipe/recipes/default.rb | 81 ++- cookbooks/test_recipe/recipes/ruby_only.rb | 41 ++ gem_install.ps1 | 0 run-test.ps1 | 649 +++++++++++++++++---- test-dll-remediation.ps1 | 429 ++++++++++++++ 18 files changed, 3846 insertions(+), 183 deletions(-) create mode 100644 .gitignore create mode 100644 Dockerfile.msi create mode 100644 advanced-dll-analysis.ps1 create mode 100644 analyze-dll-changes-fixed.ps1 create mode 100644 analyze-dll-changes-new.ps1 create mode 100644 analyze-dll-changes.ps1 create mode 100644 compare-dlls.ps1 create mode 100644 compare-gem-install-fixed.ps1 create mode 100644 compare-gem-install.ps1 create mode 100644 compare-gem-pristine.ps1 create mode 100644 cookbooks/test_recipe/recipes/ruby_only.rb create mode 100644 gem_install.ps1 create mode 100644 test-dll-remediation.ps1 diff --git a/.dockerignore b/.dockerignore index e28c9a4..d3f0eef 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,3 +6,5 @@ README.md shared/ *.log *.tmp +*.msi +!chef-installer.msi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab668a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +omnibus*.msi +shared/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 642863f..0cb0c1e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,10 +3,35 @@ FROM mcr.microsoft.com/windows/servercore:ltsc2022 # Set Chef version as build argument with default value ARG CHEF_VERSION=18.8.11 +ARG INSTALL_DUMPBIN=False +ENV INSTALL_DUMPBIN=${INSTALL_DUMPBIN} # Set up PowerShell execution policy SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] +# Conditionally install Visual Studio Build Tools for dumpbin +RUN if ($env:INSTALL_DUMPBIN -eq 'True') { ` + Write-Host 'Installing Visual Studio Build Tools for dumpbin...'; ` + Write-Host 'Downloading installer...'; ` + Invoke-WebRequest -Uri 'https://aka.ms/vs/17/release/vs_buildtools.exe' -OutFile 'vs_buildtools.exe'; ` + Write-Host 'Running VS Build Tools installer (this will take several minutes)...'; ` + $process = Start-Process vs_buildtools.exe -ArgumentList '--quiet', '--wait', '--norestart', '--nocache', '--installPath', \"${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\BuildTools\", '--add', 'Microsoft.VisualStudio.Workload.VCTools', '--includeRecommended' -Wait -PassThru; ` + Write-Host \"Installer exit code: $($process.ExitCode)\"; ` + Remove-Item vs_buildtools.exe -Force -ErrorAction SilentlyContinue; ` + Write-Host 'Attempting to add dumpbin to PATH if it was installed...'; ` + $vsPath = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC'; ` + if (Test-Path $vsPath) { ` + $msvcVersion = (Get-ChildItem $vsPath | Sort-Object Name -Descending | Select-Object -First 1).Name; ` + $dumpbinPath = Join-Path (Join-Path (Join-Path $vsPath $msvcVersion) 'bin\Hostx64') 'x64'; ` + [Environment]::SetEnvironmentVariable('PATH', $env:PATH + ';' + $dumpbinPath, [EnvironmentVariableTarget]::Machine); ` + Write-Host \"Added dumpbin to PATH: $dumpbinPath\"; ` + } else { ` + Write-Host 'VS Build Tools directory not found - dumpbin may not be available'; ` + } ` + } else { ` + Write-Host 'Skipping Visual Studio Build Tools installation (INSTALL_DUMPBIN=False)'; ` + } + # Install Chef via Omnitruck RUN Write-Host \"Installing Chef version $env:CHEF_VERSION...\"; ` [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; ` @@ -18,21 +43,6 @@ RUN Write-Host \"Installing Chef version $env:CHEF_VERSION...\"; ` # Accept Chef license ENV CHEF_LICENSE=accept-silent -# Verify Chef installation and search for Chef.PowerShell.Wrapper.dll -RUN Write-Host 'Verifying Chef installation...'; ` - chef-client --version; ` - Write-Host 'Searching for Chef.PowerShell.Wrapper.dll...'; ` - $wrapperDlls = Get-ChildItem -Path C:\opscode\chef -Include 'Chef.PowerShell.Wrapper.dll' -Recurse -ErrorAction SilentlyContinue; ` - if ($wrapperDlls) { ` - $wrapperDlls | ForEach-Object { ` - Write-Host "Found: $($_.FullName)"; ` - Write-Host " Size: $($_.Length) bytes"; ` - Write-Host " LastWriteTime: $($_.LastWriteTime)"; ` - } ` - } else { ` - Write-Host 'Chef.PowerShell.Wrapper.dll not found'; ` - } - # Create directories for Chef RUN New-Item -ItemType Directory -Force -Path C:\chef; ` New-Item -ItemType Directory -Force -Path C:\cookbooks; ` diff --git a/Dockerfile.msi b/Dockerfile.msi new file mode 100644 index 0000000..e7c17b4 --- /dev/null +++ b/Dockerfile.msi @@ -0,0 +1,53 @@ +# escape=` +FROM mcr.microsoft.com/windows/servercore:ltsc2019 + +ARG INSTALL_DUMPBIN=False +ENV INSTALL_DUMPBIN=${INSTALL_DUMPBIN} + +# Set up PowerShell execution policy +SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] + +# Conditionally install Visual Studio Build Tools for dumpbin +RUN if ($env:INSTALL_DUMPBIN -eq 'True') { ` + Write-Host 'Installing Visual Studio Build Tools for dumpbin...'; ` + Write-Host 'Downloading installer...'; ` + Invoke-WebRequest -Uri 'https://aka.ms/vs/17/release/vs_buildtools.exe' -OutFile 'vs_buildtools.exe'; ` + Write-Host 'Running VS Build Tools installer (this will take several minutes)...'; ` + $process = Start-Process vs_buildtools.exe -ArgumentList '--quiet', '--wait', '--norestart', '--nocache', '--installPath', \"${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\BuildTools\", '--add', 'Microsoft.VisualStudio.Workload.VCTools', '--includeRecommended' -Wait -PassThru; ` + Write-Host \"Installer exit code: $($process.ExitCode)\"; ` + Remove-Item vs_buildtools.exe -Force -ErrorAction SilentlyContinue; ` + Write-Host 'Attempting to add dumpbin to PATH if it was installed...'; ` + $vsPath = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC'; ` + if (Test-Path $vsPath) { ` + $msvcVersion = (Get-ChildItem $vsPath | Sort-Object Name -Descending | Select-Object -First 1).Name; ` + $dumpbinPath = Join-Path (Join-Path (Join-Path $vsPath $msvcVersion) 'bin\Hostx64') 'x64'; ` + [Environment]::SetEnvironmentVariable('PATH', $env:PATH + ';' + $dumpbinPath, [EnvironmentVariableTarget]::Machine); ` + Write-Host \"Added dumpbin to PATH: $dumpbinPath\"; ` + } else { ` + Write-Host 'VS Build Tools directory not found - dumpbin may not be available'; ` + } ` + } else { ` + Write-Host 'Skipping Visual Studio Build Tools installation (INSTALL_DUMPBIN=False)'; ` + } + +# Copy MSI file to container +COPY chef-installer.msi C:\chef-installer.msi + +# Install Chef from MSI +RUN Write-Host 'Installing Chef from MSI...'; ` + Start-Process msiexec.exe -ArgumentList '/i', 'C:\chef-installer.msi', '/qn', '/norestart' -Wait; ` + Remove-Item C:\chef-installer.msi -Force + +# Accept Chef license +ENV CHEF_LICENSE=accept-silent + +# Create directories for Chef +RUN New-Item -ItemType Directory -Force -Path C:\chef; ` + New-Item -ItemType Directory -Force -Path C:\cookbooks; ` + New-Item -ItemType Directory -Force -Path C:\shared + +# Set working directory +WORKDIR C:\chef + +# Default command +CMD ["powershell"] diff --git a/README.md b/README.md index bf035e2..272c18d 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ This project sets up Windows Docker containers with Windows Server Core 2022 and ### Option 1: Using the PowerShell Script (Recommended) +#### Test Two Versions (Default) + Run the main test script with default versions: ```powershell @@ -40,6 +42,50 @@ Or specify custom Chef versions: .\run-test.ps1 -ChefVersion1 "18.8.11" -ChefVersion2 "18.8.46" ``` +#### Test a Single Version + +```powershell +.\run-test.ps1 -ChefVersion1 "18.8.11" -SingleVersion +``` + +#### Test with an MSI File + +Place your Chef MSI file in the project root and run: + +```powershell +.\run-test.ps1 -MsiFile "chef-18.8.11-1-x64.msi" +``` + +Or with a full path: + +```powershell +.\run-test.ps1 -MsiFile "C:\downloads\chef-18.8.11-1-x64.msi" +``` + +#### Enable DLL Diagnostics + +Search for DLLs without running dumpbin: + +```powershell +.\run-test.ps1 -FindDLLs +``` + +Search for DLLs and analyze Chef.PowerShell.Wrapper.dll with dumpbin: + +```powershell +.\run-test.ps1 -FindDLLs -UseDumpbin +``` + +The script will search for the following DLLs: +- KERNEL32.dll +- VCRUNTIME140.dll +- api-ms-win-crt-runtime-l1-1-0.dll +- api-ms-win-crt-heap-l1-1-0.dll +- MSVCP140.dll +- mscoree.dll + +Note: Using `-UseDumpbin` will install Visual Studio Build Tools (~6GB) which significantly increases build time. Use this only when you need detailed dependency analysis. + ### Option 2: Using Docker Compose ```powershell @@ -74,38 +120,99 @@ Get-ChildItem .\shared\chef-*.txt | ForEach-Object { Get-Content $_ } ## What the Script Does 1. **Creates Shared Volume**: Sets up a directory for output files -2. **Builds Docker Images**: Creates Windows Server Core 2022 images with specified Chef versions installed via Omnitruck +2. **Builds Docker Images**: Creates Windows Server Core 2022 images with specified Chef versions installed via: + - Omnitruck (default for version numbers) + - MSI file (when using -MsiFile parameter) 3. **Runs Containers**: Executes Chef client in zero mode with the test recipe -4. **Searches for Chef.PowerShell.Wrapper.dll**: Before running Chef recipes, the script searches for this DLL and outputs location, size, and timestamp -5. **Test Recipe Execution**: The recipe runs a PowerShell script that: +4. **Searches for DLLs** (optional, with -FindDLLs flag): + - Chef.PowerShell.Wrapper.dll (in C:\opscode\chef) + - KERNEL32.dll + - VCRUNTIME140.dll + - api-ms-win-crt-runtime-l1-1-0.dll + - api-ms-win-crt-heap-l1-1-0.dll + - MSVCP140.dll + - mscoree.dll +5. **Runs dumpbin** (optional, with -UseDumpbin flag): Analyzes Chef.PowerShell.Wrapper.dll dependencies +6. **Test Recipe Execution**: The recipe runs a PowerShell script that: - Retrieves the current Chef version - - Searches for Chef.PowerShell.Wrapper.dll - - Outputs version and DLL information to a file on the shared volume + - Optionally searches for DLLs and runs diagnostic tools + - Outputs version and diagnostic information to a file on the shared volume - File is named `chef-{version}.txt` -6. **Validates Results**: Checks the shared volume for both output files and displays their contents +7. **Validates Results**: Checks the shared volume for output files and displays their contents + +## Usage Modes + +### Two Version Comparison (Default) +Compare two Chef versions installed via Omnitruck: +```powershell +.\run-test.ps1 -ChefVersion1 "18.8.11" -ChefVersion2 "18.8.46" +``` + +### Single Version Test +Test a single Chef version: +```powershell +.\run-test.ps1 -ChefVersion1 "17.10.0" -SingleVersion +``` + +### MSI Installation Test +Test a Chef MSI installer: +```powershell +.\run-test.ps1 -MsiFile "chef-18.8.11-1-x64.msi" +``` + +### With DLL Diagnostics +Test with DLL search (fast): +```powershell +.\run-test.ps1 -FindDLLs +``` + +Test with DLL search and dumpbin analysis (slow, installs VS Build Tools): +```powershell +.\run-test.ps1 -FindDLLs -UseDumpbin +``` + +Combine with MSI testing: +```powershell +.\run-test.ps1 -MsiFile "chef-18.8.11-1-x64.msi" -FindDLLs -UseDumpbin +``` ## Output Files Each container generates a file named `chef-{version}.txt` in the `shared` directory containing: - Chef version +- Installation method (Omnitruck or MSI) - Computer name (container name) - Timestamp - Chef client path -- Chef.PowerShell.Wrapper.dll search results (path, size, last write time) +- Chef.PowerShell.Wrapper.dll search results (if -FindDLLs is used) +- Other DLL search results (if -FindDLLs is used) +- Dumpbin dependency analysis (if -UseDumpbin is used) Example output file content: ``` Chef Version: 18.8.11 +Installation Method: Omnitruck Computer Name: CONTAINER123 Timestamp: 2025-10-22 10:30:45 Chef Client Path: C:\opscode\chef\bin\chef-client -Chef.PowerShell.Wrapper.dll Search Results: - Path: C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-18.8.11\lib\Chef.PowerShell.Wrapper.dll +Chef.PowerShell.Wrapper.dll Search Results (Pre-Chef Run): + Path: C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-18.1.0\bin\ruby_bin_folder\AMD64\Chef.PowerShell.Wrapper.dll Size: 12345 bytes LastWriteTime: 10/22/2025 10:15:30 + +Dumpbin Output: +Microsoft (R) COFF/PE Dumper Version 14.35.32215.0 +... + +DLL Search Results: +KERNEL32.dll found at: + C:\Windows\System32\KERNEL32.dll +VCRUNTIME140.dll found at: + C:\opscode\chef\embedded\bin\VCRUNTIME140.dll +... ``` ## Troubleshooting diff --git a/advanced-dll-analysis.ps1 b/advanced-dll-analysis.ps1 new file mode 100644 index 0000000..0708457 --- /dev/null +++ b/advanced-dll-analysis.ps1 @@ -0,0 +1,229 @@ +# Advanced DLL dependency analysis and alternative remediation approaches +param( + [string]$MsiFile = "omnibus-ruby_chef_pkg_chef-client-18.8.50-1-x64.msi" +) + +$ErrorActionPreference = "Stop" + +Write-Host "=== Advanced Chef PowerShell DLL Dependency Analysis ===" -ForegroundColor Cyan +Write-Host "" + +# Create shared directory if it doesn't exist +$sharedDir = Join-Path $PSScriptRoot "shared" +if (-not (Test-Path $sharedDir)) { + New-Item -ItemType Directory -Path $sharedDir | Out-Null +} + +# Copy MSI to temporary location for Docker build +$tempMsi = Join-Path $PSScriptRoot "chef-installer.msi" +Copy-Item $MsiFile $tempMsi -Force + +try { + # Build Docker image with dumpbin tools for DLL analysis + Write-Host "=== Building Chef MSI Docker Image with Analysis Tools ===" -ForegroundColor Green + docker build -f Dockerfile.msi --build-arg INSTALL_DUMPBIN=True -t chef-dll-analysis:latest . + + if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to build Chef analysis image" -ForegroundColor Red + exit 1 + } + + # Create comprehensive analysis script + $analysisScript = @' +Write-Host "=======================================================================" +Write-Host "Advanced Chef PowerShell DLL Dependency Analysis" +Write-Host "=======================================================================" + +$dllPath = "C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-18.6.3\bin\ruby_bin_folder\AMD64\Chef.PowerShell.Wrapper.dll" + +Write-Host "" +Write-Host "=== DLL Existence and Properties ===" +if (Test-Path $dllPath) { + $dllInfo = Get-Item $dllPath + Write-Host "✓ DLL found: $dllPath" + Write-Host " Size: $($dllInfo.Length) bytes" + Write-Host " LastWriteTime: $($dllInfo.LastWriteTime)" + Write-Host " Attributes: $($dllInfo.Attributes)" +} else { + Write-Host "✗ DLL not found: $dllPath" + exit 1 +} + +Write-Host "" +Write-Host "=== DLL Dependencies Analysis (using dumpbin) ===" +try { + $dumpbinOutput = & dumpbin.exe /dependents $dllPath 2>&1 | Out-String + Write-Host "Dependencies found:" + Write-Host $dumpbinOutput +} catch { + Write-Host "Could not run dumpbin: $($_.Exception.Message)" +} + +Write-Host "" +Write-Host "=== System DLL Search Paths ===" +$env:PATH -split ';' | Where-Object { $_ -and (Test-Path $_) } | ForEach-Object { + Write-Host " $($_)" +} + +Write-Host "" +Write-Host "=== .NET Framework Analysis ===" +try { + $netVersions = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP" -Recurse | + Get-ItemProperty -Name Version -ErrorAction SilentlyContinue | + Where-Object { $_.Version } | + Select-Object PSPath, Version + Write-Host ".NET Framework versions installed:" + $netVersions | ForEach-Object { Write-Host " $($_.Version) at $($_.PSPath)" } +} catch { + Write-Host "Could not enumerate .NET versions: $($_.Exception.Message)" +} + +Write-Host "" +Write-Host "=== Visual C++ Redistributables Check ===" +try { + $vcredist = Get-WmiObject -Class Win32_Product | Where-Object { + $_.Name -like "*Visual C++*" -or $_.Name -like "*Microsoft Visual C++*" + } | Select-Object Name, Version + if ($vcredist) { + Write-Host "Visual C++ redistributables found:" + $vcredist | ForEach-Object { Write-Host " $($_.Name) - $($_.Version)" } + } else { + Write-Host "No Visual C++ redistributables found via WMI" + } +} catch { + Write-Host "Could not check VC++ redistributables: $($_.Exception.Message)" +} + +Write-Host "" +Write-Host "=== Alternative Gem Operations ===" + +# Try gem list to see if gem commands work at all +Write-Host "Testing basic gem functionality..." +try { + $gemList = & "C:\opscode\chef\embedded\bin\gem.cmd" list chef-powershell 2>&1 | Out-String + Write-Host "Gem list output:" + Write-Host $gemList +} catch { + Write-Host "Gem list failed: $($_.Exception.Message)" +} + +# Try gem info +Write-Host "" +Write-Host "Getting gem info..." +try { + $gemInfo = & "C:\opscode\chef\embedded\bin\gem.cmd" info chef-powershell 2>&1 | Out-String + Write-Host "Gem info output:" + Write-Host $gemInfo +} catch { + Write-Host "Gem info failed: $($_.Exception.Message)" +} + +# Try gem pristine with .cmd extension +Write-Host "" +Write-Host "Attempting gem pristine with .cmd extension..." +try { + $pristineCmd = & "C:\opscode\chef\embedded\bin\gem.cmd" pristine chef-powershell 2>&1 | Out-String + Write-Host "Gem pristine (.cmd) output:" + Write-Host $pristineCmd +} catch { + Write-Host "Gem pristine (.cmd) failed: $($_.Exception.Message)" +} + +Write-Host "" +Write-Host "=== File Permissions Analysis ===" +try { + $acl = Get-Acl $dllPath + Write-Host "DLL file permissions:" + $acl.Access | ForEach-Object { + Write-Host " $($_.IdentityReference): $($_.AccessControlType) - $($_.FileSystemRights)" + } +} catch { + Write-Host "Could not get file permissions: $($_.Exception.Message)" +} + +Write-Host "" +Write-Host "=== Manual DLL Loading Test ===" +try { + Add-Type -TypeDefinition @" + using System; + using System.Runtime.InteropServices; + public class DllTest { + [DllImport("kernel32.dll")] + public static extern IntPtr LoadLibrary(string dllToLoad); + [DllImport("kernel32.dll")] + public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); + [DllImport("kernel32.dll")] + public static extern bool FreeLibrary(IntPtr hModule); + } +"@ + + $handle = [DllTest]::LoadLibrary($dllPath) + if ($handle -ne [IntPtr]::Zero) { + Write-Host "✓ Manual DLL loading succeeded" + [DllTest]::FreeLibrary($handle) | Out-Null + } else { + $error = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() + Write-Host "✗ Manual DLL loading failed with error: $error" + } +} catch { + Write-Host "Manual DLL loading test failed: $($_.Exception.Message)" +} + +Write-Host "" +Write-Host "=== Alternative Remediation Attempts ===" + +# Try gem uninstall and reinstall +Write-Host "Attempting gem uninstall/reinstall..." +try { + Write-Host "Uninstalling chef-powershell..." + & "C:\opscode\chef\embedded\bin\gem.cmd" uninstall chef-powershell --force 2>&1 | Out-String | Write-Host + + Write-Host "Reinstalling chef-powershell..." + & "C:\opscode\chef\embedded\bin\gem.cmd" install chef-powershell 2>&1 | Out-String | Write-Host + + Write-Host "Checking if DLL exists after reinstall..." + if (Test-Path $dllPath) { + $dllInfoNew = Get-Item $dllPath + Write-Host "✓ DLL recreated: Size: $($dllInfoNew.Length), Time: $($dllInfoNew.LastWriteTime)" + } else { + Write-Host "✗ DLL not recreated after reinstall" + } + +} catch { + Write-Host "Gem uninstall/reinstall failed: $($_.Exception.Message)" +} + +Write-Host "" +Write-Host "=== Final Chef Test ===" +try { + Write-Host "Testing Chef after remediation attempts..." + $chefResult = & chef-client -z -o recipe[test_recipe] --chef-license accept-silent 2>&1 | Out-String + + if ($LASTEXITCODE -eq 0) { + Write-Host "✓ Chef test succeeded after remediation!" + } else { + Write-Host "✗ Chef test still fails after remediation" + Write-Host "Error output:" + Write-Host $chefResult + } +} catch { + Write-Host "Final Chef test failed: $($_.Exception.Message)" +} + +Write-Host "" +Write-Host "=======================================================================" +'@ + + # Run the comprehensive analysis + Write-Host "`n=== Running Advanced DLL Analysis ===" -ForegroundColor Green + docker run --rm -e CHEF_LICENSE=accept-silent -v "${PWD}\shared:C:\shared" -v "${PWD}\cookbooks:C:\cookbooks" chef-dll-analysis:latest powershell -Command $analysisScript + +} +finally { + # Clean up temporary MSI file + if (Test-Path $tempMsi) { + Remove-Item $tempMsi -Force -ErrorAction SilentlyContinue + } +} + +Write-Host "`n=== Advanced DLL Analysis Complete ===" -ForegroundColor Cyan \ No newline at end of file diff --git a/analyze-dll-changes-fixed.ps1 b/analyze-dll-changes-fixed.ps1 new file mode 100644 index 0000000..62ef4b7 --- /dev/null +++ b/analyze-dll-changes-fixed.ps1 @@ -0,0 +1,321 @@ +# Script to analyze differences between pre and post dumpbin DLL states +# Identifies what was added and compares with ruby_bin folder DLLs + +param( + [string]$PreFile = ".\shared\predumpbin.txt", + [string]$PostFile = ".\shared\postdumpbin.txt" +) + +$ErrorActionPreference = "Stop" + +Write-Host "=== DLL Change Analysis Script ===" -ForegroundColor Cyan +Write-Host "" + +# Validate input files exist +if (-not (Test-Path $PreFile)) { + Write-Host "ERROR: Pre-dumpbin file not found: $PreFile" -ForegroundColor Red + exit 1 +} + +if (-not (Test-Path $PostFile)) { + Write-Host "ERROR: Post-dumpbin file not found: $PostFile" -ForegroundColor Red + exit 1 +} + +Write-Host "Analyzing files:" -ForegroundColor Yellow +Write-Host " Pre: $PreFile" +Write-Host " Post: $PostFile" +Write-Host "" + +# Helper function to parse DLL information from file +function Parse-DllInfo { + param([string]$FilePath) + + $content = Get-Content $FilePath -Raw + $dllInfo = @{} + + # Split by DLL sections + $sections = $content -split '(?m)^=== (.+?\.dll) ===$' + + for ($i = 1; $i -lt $sections.Count; $i += 2) { + $dllName = $sections[$i].Trim() + $dllContent = $sections[$i + 1] + + if ($dllContent -notmatch 'Not found') { + # Parse multiple instances of the same DLL + $instances = $dllContent -split '(?m)^Path: ' + + foreach ($instance in $instances) { + if ($instance.Trim() -eq '') { continue } + + # Extract path + if ($instance -match '^(.+?)[\r\n]') { + $path = $matches[1].Trim() + + # Extract size + $size = if ($instance -match 'Size: (\d+)') { $matches[1] } else { 'Unknown' } + + # Extract MD5 + $md5 = if ($instance -match 'MD5: ([A-F0-9]+)') { $matches[1] } else { 'Unknown' } + + # Extract SHA256 + $sha256 = if ($instance -match 'SHA256: ([A-F0-9]+)') { $matches[1] } else { 'Unknown' } + + # Extract LastWriteTime + $lastWrite = if ($instance -match 'LastWriteTime: (.+?)[\r\n]') { $matches[1].Trim() } else { 'Unknown' } + + # Create unique key for this DLL instance + $key = "$dllName|$path" + + $dllInfo[$key] = @{ + Name = $dllName + Path = $path + Size = $size + MD5 = $md5 + SHA256 = $sha256 + LastWriteTime = $lastWrite + } + } + } + } + } + + return $dllInfo +} + +Write-Host "Parsing pre-dumpbin state..." -ForegroundColor Yellow +$preDlls = Parse-DllInfo -FilePath $PreFile + +Write-Host "Parsing post-dumpbin state..." -ForegroundColor Yellow +$postDlls = Parse-DllInfo -FilePath $PostFile + +Write-Host "" +Write-Host "=== Analysis Results ===" -ForegroundColor Cyan +Write-Host "" + +# Find added DLLs +$addedDlls = @{} +foreach ($key in $postDlls.Keys) { + if (-not $preDlls.ContainsKey($key)) { + $addedDlls[$key] = $postDlls[$key] + } +} + +# Find removed DLLs (shouldn't happen, but check anyway) +$removedDlls = @{} +foreach ($key in $preDlls.Keys) { + if (-not $postDlls.ContainsKey($key)) { + $removedDlls[$key] = $preDlls[$key] + } +} + +# Find changed DLLs (same path, different checksum) +$changedDlls = @{} +foreach ($key in $preDlls.Keys) { + if ($postDlls.ContainsKey($key)) { + $pre = $preDlls[$key] + $post = $postDlls[$key] + + if ($pre.SHA256 -ne $post.SHA256) { + $changedDlls[$key] = @{ + Pre = $pre + Post = $post + } + } + } +} + +# Display results +Write-Host "Summary:" -ForegroundColor White +Write-Host " Total DLLs before: $($preDlls.Count)" -ForegroundColor White +Write-Host " Total DLLs after: $($postDlls.Count)" -ForegroundColor White +Write-Host " Added: $($addedDlls.Count)" -ForegroundColor Green +Write-Host " Removed: $($removedDlls.Count)" -ForegroundColor Red +Write-Host " Changed: $($changedDlls.Count)" -ForegroundColor Yellow +Write-Host "" + +# Report added DLLs +if ($addedDlls.Count -gt 0) { + Write-Host "=== ADDED DLLs ===" -ForegroundColor Green + Write-Host "" + + foreach ($key in ($addedDlls.Keys | Sort-Object)) { + $dll = $addedDlls[$key] + Write-Host " $($dll.Name)" -ForegroundColor Cyan + Write-Host " Path: $($dll.Path)" + Write-Host " Size: $($dll.Size) bytes" + Write-Host " LastWriteTime: $($dll.LastWriteTime)" + Write-Host " MD5: $($dll.MD5)" + Write-Host " SHA256: $($dll.SHA256)" + Write-Host "" + } +} + +# Report removed DLLs +if ($removedDlls.Count -gt 0) { + Write-Host "=== REMOVED DLLs ===" -ForegroundColor Red + Write-Host "" + + foreach ($key in ($removedDlls.Keys | Sort-Object)) { + $dll = $removedDlls[$key] + Write-Host " $($dll.Name)" -ForegroundColor Cyan + Write-Host " Path: $($dll.Path)" + Write-Host " Size: $($dll.Size) bytes" + Write-Host " MD5: $($dll.MD5)" + Write-Host " SHA256: $($dll.SHA256)" + Write-Host "" + } +} + +# Report changed DLLs +if ($changedDlls.Count -gt 0) { + Write-Host "=== CHANGED DLLs ===" -ForegroundColor Yellow + Write-Host "" + + foreach ($key in ($changedDlls.Keys | Sort-Object)) { + $change = $changedDlls[$key] + Write-Host " $($change.Pre.Name)" -ForegroundColor Cyan + Write-Host " Path: $($change.Pre.Path)" + Write-Host "" + Write-Host " BEFORE:" -ForegroundColor Yellow + Write-Host " Size: $($change.Pre.Size) bytes" + Write-Host " LastWriteTime: $($change.Pre.LastWriteTime)" + Write-Host " MD5: $($change.Pre.MD5)" + Write-Host " SHA256: $($change.Pre.SHA256)" + Write-Host "" + Write-Host " AFTER:" -ForegroundColor Yellow + Write-Host " Size: $($change.Post.Size) bytes" + Write-Host " LastWriteTime: $($change.Post.LastWriteTime)" + Write-Host " MD5: $($change.Post.MD5)" + Write-Host " SHA256: $($change.Post.SHA256)" + Write-Host "" + } +} + +# Now compare added DLLs with ruby_bin folder equivalents +if ($addedDlls.Count -gt 0) { + Write-Host "=== COMPARISON WITH ruby_bin FOLDER ===" -ForegroundColor Cyan + Write-Host "" + + # Group DLLs by name to find ruby_bin versions + $dllsByName = @{} + foreach ($key in $postDlls.Keys) { + $dll = $postDlls[$key] + if (-not $dllsByName.ContainsKey($dll.Name)) { + $dllsByName[$dll.Name] = @() + } + $dllsByName[$dll.Name] += $dll + } + + foreach ($key in ($addedDlls.Keys | Sort-Object)) { + $addedDll = $addedDlls[$key] + $dllName = $addedDll.Name + + Write-Host "Analyzing: $dllName" -ForegroundColor Cyan + Write-Host " Added location: $($addedDll.Path)" -ForegroundColor Green + + # Find ruby_bin versions of this DLL + $rubyBinVersions = $dllsByName[$dllName] | Where-Object { + $_.Path -match 'ruby_bin.*AMD64' -and $_.Path -ne $addedDll.Path + } + + if ($rubyBinVersions) { + Write-Host " Found in ruby_bin\AMD64:" -ForegroundColor Yellow + + foreach ($rubyDll in $rubyBinVersions) { + Write-Host " Path: $($rubyDll.Path)" + + # Compare checksums + if ($rubyDll.SHA256 -eq $addedDll.SHA256) { + Write-Host " ✓ IDENTICAL (SHA256 matches)" -ForegroundColor Green + } else { + Write-Host " ✗ DIFFERENT (SHA256 mismatch)" -ForegroundColor Red + Write-Host " ruby_bin SHA256: $($rubyDll.SHA256)" + Write-Host " Added SHA256: $($addedDll.SHA256)" + + # Compare sizes + if ($rubyDll.Size -eq $addedDll.Size) { + $sizeBytes = $rubyDll.Size + Write-Host " Size: Same - $sizeBytes bytes" + } else { + $rubySize = $rubyDll.Size + $addedSize = $addedDll.Size + Write-Host " Size: Different - ruby_bin: $rubySize bytes, added: $addedSize bytes" -ForegroundColor Yellow + } + } + Write-Host "" + } + } else { + Write-Host " NOT found in ruby_bin\AMD64 folder" -ForegroundColor Magenta + Write-Host "" + } + } +} + +# Summary of findings +Write-Host "=== KEY FINDINGS ===" -ForegroundColor Cyan +Write-Host "" + +if ($addedDlls.Count -eq 0 -and $changedDlls.Count -eq 0) { + Write-Host "✓ No changes detected - Installing dumpbin did not add or modify any of the tracked DLLs" -ForegroundColor Green +} else { + if ($addedDlls.Count -gt 0) { + Write-Host "• Dumpbin installation added $($addedDlls.Count) DLL(s)" -ForegroundColor Yellow + + # Check if any added DLLs match ruby_bin versions + $identicalToRubyBin = 0 + $differentFromRubyBin = 0 + $notInRubyBin = 0 + + $dllsByName = @{} + foreach ($key in $postDlls.Keys) { + $dll = $postDlls[$key] + if (-not $dllsByName.ContainsKey($dll.Name)) { + $dllsByName[$dll.Name] = @() + } + $dllsByName[$dll.Name] += $dll + } + + foreach ($key in $addedDlls.Keys) { + $addedDll = $addedDlls[$key] + $rubyBinVersions = $dllsByName[$addedDll.Name] | Where-Object { + $_.Path -match 'ruby_bin.*AMD64' -and $_.Path -ne $addedDll.Path + } + + if ($rubyBinVersions) { + $isIdentical = $false + foreach ($rubyDll in $rubyBinVersions) { + if ($rubyDll.SHA256 -eq $addedDll.SHA256) { + $isIdentical = $true + break + } + } + + if ($isIdentical) { + $identicalToRubyBin++ + } else { + $differentFromRubyBin++ + } + } else { + $notInRubyBin++ + } + } + + if ($identicalToRubyBin -gt 0) { + Write-Host " - $identicalToRubyBin DLL(s) are IDENTICAL to ruby_bin\AMD64 versions" -ForegroundColor Green + } + if ($differentFromRubyBin -gt 0) { + Write-Host " - $differentFromRubyBin DLL(s) are DIFFERENT from ruby_bin\AMD64 versions" -ForegroundColor Red + } + if ($notInRubyBin -gt 0) { + Write-Host " - $notInRubyBin DLL(s) are NOT present in ruby_bin\AMD64 folder" -ForegroundColor Magenta + } + } + + if ($changedDlls.Count -gt 0) { + Write-Host "• Dumpbin installation modified $($changedDlls.Count) existing DLL(s)" -ForegroundColor Red + } +} + +Write-Host "" +Write-Host "=== Analysis Complete ===" -ForegroundColor Cyan diff --git a/analyze-dll-changes-new.ps1 b/analyze-dll-changes-new.ps1 new file mode 100644 index 0000000..003fe20 --- /dev/null +++ b/analyze-dll-changes-new.ps1 @@ -0,0 +1,321 @@ +# Script to analyze differences between pre and post dumpbin DLL states +# Identifies what was added and compares with ruby_bin folder DLLs + +param( + [string]$PreFile = ".\shared\predumpbin.txt", + [string]$PostFile = ".\shared\postdumpbin.txt" +) + +$ErrorActionPreference = "Stop" + +Write-Host "=== DLL Change Analysis Script ===" -ForegroundColor Cyan +Write-Host "" + +# Validate input files exist +if (-not (Test-Path $PreFile)) { + Write-Host "ERROR: Pre-dumpbin file not found: $PreFile" -ForegroundColor Red + exit 1 +} + +if (-not (Test-Path $PostFile)) { + Write-Host "ERROR: Post-dumpbin file not found: $PostFile" -ForegroundColor Red + exit 1 +} + +Write-Host "Analyzing files:" -ForegroundColor Yellow +Write-Host " Pre: $PreFile" +Write-Host " Post: $PostFile" +Write-Host "" + +# Helper function to parse DLL information from file +function Parse-DllInfo { + param([string]$FilePath) + + $content = Get-Content $FilePath -Raw + $dllInfo = @{} + + # Split by DLL sections + $sections = $content -split '(?m)^=== (.+?\.dll) ===$' + + for ($i = 1; $i -lt $sections.Count; $i += 2) { + $dllName = $sections[$i].Trim() + $dllContent = $sections[$i + 1] + + if ($dllContent -notmatch 'Not found') { + # Parse multiple instances of the same DLL + $instances = $dllContent -split '(?m)^Path: ' + + foreach ($instance in $instances) { + if ($instance.Trim() -eq '') { continue } + + # Extract path + if ($instance -match '^(.+?)[\r\n]') { + $path = $matches[1].Trim() + + # Extract size + $size = if ($instance -match 'Size: (\d+)') { $matches[1] } else { 'Unknown' } + + # Extract MD5 + $md5 = if ($instance -match 'MD5: ([A-F0-9]+)') { $matches[1] } else { 'Unknown' } + + # Extract SHA256 + $sha256 = if ($instance -match 'SHA256: ([A-F0-9]+)') { $matches[1] } else { 'Unknown' } + + # Extract LastWriteTime + $lastWrite = if ($instance -match 'LastWriteTime: (.+?)[\r\n]') { $matches[1].Trim() } else { 'Unknown' } + + # Create unique key for this DLL instance + $key = "$dllName|$path" + + $dllInfo[$key] = @{ + Name = $dllName + Path = $path + Size = $size + MD5 = $md5 + SHA256 = $sha256 + LastWriteTime = $lastWrite + } + } + } + } + } + + return $dllInfo +} + +Write-Host "Parsing pre-dumpbin state..." -ForegroundColor Yellow +$preDlls = Parse-DllInfo -FilePath $PreFile + +Write-Host "Parsing post-dumpbin state..." -ForegroundColor Yellow +$postDlls = Parse-DllInfo -FilePath $PostFile + +Write-Host "" +Write-Host "=== Analysis Results ===" -ForegroundColor Cyan +Write-Host "" + +# Find added DLLs +$addedDlls = @{} +foreach ($key in $postDlls.Keys) { + if (-not $preDlls.ContainsKey($key)) { + $addedDlls[$key] = $postDlls[$key] + } +} + +# Find removed DLLs (shouldn't happen, but check anyway) +$removedDlls = @{} +foreach ($key in $preDlls.Keys) { + if (-not $postDlls.ContainsKey($key)) { + $removedDlls[$key] = $preDlls[$key] + } +} + +# Find changed DLLs (same path, different checksum) +$changedDlls = @{} +foreach ($key in $preDlls.Keys) { + if ($postDlls.ContainsKey($key)) { + $pre = $preDlls[$key] + $post = $postDlls[$key] + + if ($pre.SHA256 -ne $post.SHA256) { + $changedDlls[$key] = @{ + Pre = $pre + Post = $post + } + } + } +} + +# Display results +Write-Host "Summary:" -ForegroundColor White +Write-Host " Total DLLs before: $($preDlls.Count)" -ForegroundColor White +Write-Host " Total DLLs after: $($postDlls.Count)" -ForegroundColor White +Write-Host " Added: $($addedDlls.Count)" -ForegroundColor Green +Write-Host " Removed: $($removedDlls.Count)" -ForegroundColor Red +Write-Host " Changed: $($changedDlls.Count)" -ForegroundColor Yellow +Write-Host "" + +# Report added DLLs +if ($addedDlls.Count -gt 0) { + Write-Host "=== ADDED DLLs ===" -ForegroundColor Green + Write-Host "" + + foreach ($key in ($addedDlls.Keys | Sort-Object)) { + $dll = $addedDlls[$key] + Write-Host " $($dll.Name)" -ForegroundColor Cyan + Write-Host " Path: $($dll.Path)" + Write-Host " Size: $($dll.Size) bytes" + Write-Host " LastWriteTime: $($dll.LastWriteTime)" + Write-Host " MD5: $($dll.MD5)" + Write-Host " SHA256: $($dll.SHA256)" + Write-Host "" + } +} + +# Report removed DLLs +if ($removedDlls.Count -gt 0) { + Write-Host "=== REMOVED DLLs ===" -ForegroundColor Red + Write-Host "" + + foreach ($key in ($removedDlls.Keys | Sort-Object)) { + $dll = $removedDlls[$key] + Write-Host " $($dll.Name)" -ForegroundColor Cyan + Write-Host " Path: $($dll.Path)" + Write-Host " Size: $($dll.Size) bytes" + Write-Host " MD5: $($dll.MD5)" + Write-Host " SHA256: $($dll.SHA256)" + Write-Host "" + } +} + +# Report changed DLLs +if ($changedDlls.Count -gt 0) { + Write-Host "=== CHANGED DLLs ===" -ForegroundColor Yellow + Write-Host "" + + foreach ($key in ($changedDlls.Keys | Sort-Object)) { + $change = $changedDlls[$key] + Write-Host " $($change.Pre.Name)" -ForegroundColor Cyan + Write-Host " Path: $($change.Pre.Path)" + Write-Host "" + Write-Host " BEFORE:" -ForegroundColor Yellow + Write-Host " Size: $($change.Pre.Size) bytes" + Write-Host " LastWriteTime: $($change.Pre.LastWriteTime)" + Write-Host " MD5: $($change.Pre.MD5)" + Write-Host " SHA256: $($change.Pre.SHA256)" + Write-Host "" + Write-Host " AFTER:" -ForegroundColor Yellow + Write-Host " Size: $($change.Post.Size) bytes" + Write-Host " LastWriteTime: $($change.Post.LastWriteTime)" + Write-Host " MD5: $($change.Post.MD5)" + Write-Host " SHA256: $($change.Post.SHA256)" + Write-Host "" + } +} + +# Now compare added DLLs with ruby_bin folder equivalents +if ($addedDlls.Count -gt 0) { + Write-Host "=== COMPARISON WITH ruby_bin FOLDER ===" -ForegroundColor Cyan + Write-Host "" + + # Group DLLs by name to find ruby_bin versions + $dllsByName = @{} + foreach ($key in $postDlls.Keys) { + $dll = $postDlls[$key] + if (-not $dllsByName.ContainsKey($dll.Name)) { + $dllsByName[$dll.Name] = @() + } + $dllsByName[$dll.Name] += $dll + } + + foreach ($key in ($addedDlls.Keys | Sort-Object)) { + $addedDll = $addedDlls[$key] + $dllName = $addedDll.Name + + Write-Host "Analyzing: $dllName" -ForegroundColor Cyan + Write-Host " Added location: $($addedDll.Path)" -ForegroundColor Green + + # Find ruby_bin versions of this DLL + $rubyBinVersions = $dllsByName[$dllName] | Where-Object { + $_.Path -match 'ruby_bin.*AMD64' -and $_.Path -ne $addedDll.Path + } + + if ($rubyBinVersions) { + Write-Host " Found in ruby_bin\AMD64:" -ForegroundColor Yellow + + foreach ($rubyDll in $rubyBinVersions) { + Write-Host " Path: $($rubyDll.Path)" + + # Compare checksums + if ($rubyDll.SHA256 -eq $addedDll.SHA256) { + Write-Host " ✓ IDENTICAL (SHA256 matches)" -ForegroundColor Green + } else { + Write-Host " ✗ DIFFERENT (SHA256 mismatch)" -ForegroundColor Red + Write-Host " ruby_bin SHA256: $($rubyDll.SHA256)" + Write-Host " Added SHA256: $($addedDll.SHA256)" + + # Compare sizes + if ($rubyDll.Size -eq $addedDll.Size) { + $sizeBytes = $rubyDll.Size + Write-Host " Size: Same - $sizeBytes bytes" + } else { + $rubySize = $rubyDll.Size + $addedSize = $addedDll.Size + Write-Host " Size: Different - ruby_bin: $rubySize bytes, added: $addedSize bytes" -ForegroundColor Yellow + } + } + Write-Host "" + } + } else { + Write-Host " NOT found in ruby_bin\AMD64 folder" -ForegroundColor Magenta + Write-Host "" + } + } +} + +# Summary of findings +Write-Host "=== KEY FINDINGS ===" -ForegroundColor Cyan +Write-Host "" + +if ($addedDlls.Count -eq 0 -and $changedDlls.Count -eq 0) { + Write-Host "✓ No changes detected - Installing dumpbin did not add or modify any of the tracked DLLs" -ForegroundColor Green +} else { + if ($addedDlls.Count -gt 0) { + Write-Host "• Dumpbin installation added $($addedDlls.Count) DLL(s)" -ForegroundColor Yellow + + # Check if any added DLLs match ruby_bin versions + $identicalToRubyBin = 0 + $differentFromRubyBin = 0 + $notInRubyBin = 0 + + $dllsByName = @{} + foreach ($key in $postDlls.Keys) { + $dll = $postDlls[$key] + if (-not $dllsByName.ContainsKey($dll.Name)) { + $dllsByName[$dll.Name] = @() + } + $dllsByName[$dll.Name] += $dll + } + + foreach ($key in $addedDlls.Keys) { + $addedDll = $addedDlls[$key] + $rubyBinVersions = $dllsByName[$addedDll.Name] | Where-Object { + $_.Path -match 'ruby_bin.*AMD64' -and $_.Path -ne $addedDll.Path + } + + if ($rubyBinVersions) { + $isIdentical = $false + foreach ($rubyDll in $rubyBinVersions) { + if ($rubyDll.SHA256 -eq $addedDll.SHA256) { + $isIdentical = $true + break + } + } + + if ($isIdentical) { + $identicalToRubyBin++ + } else { + $differentFromRubyBin++ + } + } else { + $notInRubyBin++ + } + } + + if ($identicalToRubyBin -gt 0) { + Write-Host " - $identicalToRubyBin DLL(s) are IDENTICAL to ruby_bin\AMD64 versions" -ForegroundColor Green + } + if ($differentFromRubyBin -gt 0) { + Write-Host " - $differentFromRubyBin DLL(s) are DIFFERENT from ruby_bin\AMD64 versions" -ForegroundColor Red + } + if ($notInRubyBin -gt 0) { + Write-Host " - $notInRubyBin DLL(s) are NOT present in ruby_bin\AMD64 folder" -ForegroundColor Magenta + } + } + + if ($changedDlls.Count -gt 0) { + Write-Host "• Dumpbin installation modified $($changedDlls.Count) existing DLL(s)" -ForegroundColor Red + } +} + +Write-Host "" +Write-Host "=== Analysis Complete ===" -ForegroundColor Cyan diff --git a/analyze-dll-changes.ps1 b/analyze-dll-changes.ps1 new file mode 100644 index 0000000..57f0f91 --- /dev/null +++ b/analyze-dll-changes.ps1 @@ -0,0 +1,321 @@ +# Script to analyze differences between pre and post dumpbin DLL states +# Identifies what was added and compares with ruby_bin folder DLLs + +param( + [string]$PreFile = ".\shared\predumpbin.txt", + [string]$PostFile = ".\shared\postdumpbin.txt" +) + +$ErrorActionPreference = "Stop" + +Write-Host "=== DLL Change Analysis Script ===" -ForegroundColor Cyan +Write-Host "" + +# Validate input files exist +if (-not (Test-Path $PreFile)) { + Write-Host "ERROR: Pre-dumpbin file not found: $PreFile" -ForegroundColor Red + exit 1 +} + +if (-not (Test-Path $PostFile)) { + Write-Host "ERROR: Post-dumpbin file not found: $PostFile" -ForegroundColor Red + exit 1 +} + +Write-Host "Analyzing files:" -ForegroundColor Yellow +Write-Host " Pre: $PreFile" +Write-Host " Post: $PostFile" +Write-Host "" + +# Helper function to parse DLL information from file +function Parse-DllInfo { + param([string]$FilePath) + + $content = Get-Content $FilePath -Raw + $dllInfo = @{} + + # Split by DLL sections + $sections = $content -split '(?m)^=== (.+?\.dll) ===$' + + for ($i = 1; $i -lt $sections.Count; $i += 2) { + $dllName = $sections[$i].Trim() + $dllContent = $sections[$i + 1] + + if ($dllContent -notmatch 'Not found') { + # Parse multiple instances of the same DLL + $instances = $dllContent -split '(?m)^Path: ' + + foreach ($instance in $instances) { + if ($instance.Trim() -eq '') { continue } + + # Extract path + if ($instance -match '^(.+?)[\r\n]') { + $path = $matches[1].Trim() + + # Extract size + $size = if ($instance -match 'Size: (\d+)') { $matches[1] } else { 'Unknown' } + + # Extract MD5 + $md5 = if ($instance -match 'MD5: ([A-F0-9]+)') { $matches[1] } else { 'Unknown' } + + # Extract SHA256 + $sha256 = if ($instance -match 'SHA256: ([A-F0-9]+)') { $matches[1] } else { 'Unknown' } + + # Extract LastWriteTime + $lastWrite = if ($instance -match 'LastWriteTime: (.+?)[\r\n]') { $matches[1].Trim() } else { 'Unknown' } + + # Create unique key for this DLL instance + $key = "$dllName|$path" + + $dllInfo[$key] = @{ + Name = $dllName + Path = $path + Size = $size + MD5 = $md5 + SHA256 = $sha256 + LastWriteTime = $lastWrite + } + } + } + } + } + + return $dllInfo +} + +Write-Host "Parsing pre-dumpbin state..." -ForegroundColor Yellow +$preDlls = Parse-DllInfo -FilePath $PreFile + +Write-Host "Parsing post-dumpbin state..." -ForegroundColor Yellow +$postDlls = Parse-DllInfo -FilePath $PostFile + +Write-Host "" +Write-Host "=== Analysis Results ===" -ForegroundColor Cyan +Write-Host "" + +# Find added DLLs +$addedDlls = @{} +foreach ($key in $postDlls.Keys) { + if (-not $preDlls.ContainsKey($key)) { + $addedDlls[$key] = $postDlls[$key] + } +} + +# Find removed DLLs (shouldn't happen, but check anyway) +$removedDlls = @{} +foreach ($key in $preDlls.Keys) { + if (-not $postDlls.ContainsKey($key)) { + $removedDlls[$key] = $preDlls[$key] + } +} + +# Find changed DLLs (same path, different checksum) +$changedDlls = @{} +foreach ($key in $preDlls.Keys) { + if ($postDlls.ContainsKey($key)) { + $pre = $preDlls[$key] + $post = $postDlls[$key] + + if ($pre.SHA256 -ne $post.SHA256) { + $changedDlls[$key] = @{ + Pre = $pre + Post = $post + } + } + } +} + +# Display results +Write-Host "Summary:" -ForegroundColor White +Write-Host " Total DLLs before: $($preDlls.Count)" -ForegroundColor White +Write-Host " Total DLLs after: $($postDlls.Count)" -ForegroundColor White +Write-Host " Added: $($addedDlls.Count)" -ForegroundColor Green +Write-Host " Removed: $($removedDlls.Count)" -ForegroundColor Red +Write-Host " Changed: $($changedDlls.Count)" -ForegroundColor Yellow +Write-Host "" + +# Report added DLLs +if ($addedDlls.Count -gt 0) { + Write-Host "=== ADDED DLLs ===" -ForegroundColor Green + Write-Host "" + + foreach ($key in ($addedDlls.Keys | Sort-Object)) { + $dll = $addedDlls[$key] + Write-Host " $($dll.Name)" -ForegroundColor Cyan + Write-Host " Path: $($dll.Path)" + Write-Host " Size: $($dll.Size) bytes" + Write-Host " LastWriteTime: $($dll.LastWriteTime)" + Write-Host " MD5: $($dll.MD5)" + Write-Host " SHA256: $($dll.SHA256)" + Write-Host "" + } +} + +# Report removed DLLs +if ($removedDlls.Count -gt 0) { + Write-Host "=== REMOVED DLLs ===" -ForegroundColor Red + Write-Host "" + + foreach ($key in ($removedDlls.Keys | Sort-Object)) { + $dll = $removedDlls[$key] + Write-Host " $($dll.Name)" -ForegroundColor Cyan + Write-Host " Path: $($dll.Path)" + Write-Host " Size: $($dll.Size) bytes" + Write-Host " MD5: $($dll.MD5)" + Write-Host " SHA256: $($dll.SHA256)" + Write-Host "" + } +} + +# Report changed DLLs +if ($changedDlls.Count -gt 0) { + Write-Host "=== CHANGED DLLs ===" -ForegroundColor Yellow + Write-Host "" + + foreach ($key in ($changedDlls.Keys | Sort-Object)) { + $change = $changedDlls[$key] + Write-Host " $($change.Pre.Name)" -ForegroundColor Cyan + Write-Host " Path: $($change.Pre.Path)" + Write-Host "" + Write-Host " BEFORE:" -ForegroundColor Yellow + Write-Host " Size: $($change.Pre.Size) bytes" + Write-Host " LastWriteTime: $($change.Pre.LastWriteTime)" + Write-Host " MD5: $($change.Pre.MD5)" + Write-Host " SHA256: $($change.Pre.SHA256)" + Write-Host "" + Write-Host " AFTER:" -ForegroundColor Yellow + Write-Host " Size: $($change.Post.Size) bytes" + Write-Host " LastWriteTime: $($change.Post.LastWriteTime)" + Write-Host " MD5: $($change.Post.MD5)" + Write-Host " SHA256: $($change.Post.SHA256)" + Write-Host "" + } +} + +# Now compare added DLLs with ruby_bin folder equivalents +if ($addedDlls.Count -gt 0) { + Write-Host "=== COMPARISON WITH ruby_bin FOLDER ===" -ForegroundColor Cyan + Write-Host "" + + # Group DLLs by name to find ruby_bin versions + $dllsByName = @{} + foreach ($key in $postDlls.Keys) { + $dll = $postDlls[$key] + if (-not $dllsByName.ContainsKey($dll.Name)) { + $dllsByName[$dll.Name] = @() + } + $dllsByName[$dll.Name] += $dll + } + + foreach ($key in ($addedDlls.Keys | Sort-Object)) { + $addedDll = $addedDlls[$key] + $dllName = $addedDll.Name + + Write-Host "Analyzing: $dllName" -ForegroundColor Cyan + Write-Host " Added location: $($addedDll.Path)" -ForegroundColor Green + + # Find ruby_bin versions of this DLL + $rubyBinVersions = $dllsByName[$dllName] | Where-Object { + $_.Path -match 'ruby_bin.*AMD64' -and $_.Path -ne $addedDll.Path + } + + if ($rubyBinVersions) { + Write-Host " Found in ruby_bin\AMD64:" -ForegroundColor Yellow + + foreach ($rubyDll in $rubyBinVersions) { + Write-Host " Path: $($rubyDll.Path)" + + # Compare checksums + if ($rubyDll.SHA256 -eq $addedDll.SHA256) { + Write-Host " IDENTICAL (SHA256 matches)" -ForegroundColor Green + } else { + Write-Host " DIFFERENT (SHA256 mismatch)" -ForegroundColor Red + Write-Host " ruby_bin SHA256: $($rubyDll.SHA256)" + Write-Host " Added SHA256: $($addedDll.SHA256)" + + # Compare sizes + if ($rubyDll.Size -eq $addedDll.Size) { + $sizeBytes = $rubyDll.Size + Write-Host " Size: Same - $sizeBytes bytes" + } else { + $rubySize = $rubyDll.Size + $addedSize = $addedDll.Size + Write-Host " Size: Different - ruby_bin: $rubySize bytes, added: $addedSize bytes" -ForegroundColor Yellow + } + } + Write-Host "" + } + } else { + Write-Host " NOT found in ruby_bin\AMD64 folder" -ForegroundColor Magenta + Write-Host "" + } + } +} + +# Summary of findings +Write-Host "=== KEY FINDINGS ===" -ForegroundColor Cyan +Write-Host "" + +if ($addedDlls.Count -eq 0 -and $changedDlls.Count -eq 0) { + Write-Host "No changes detected - Installing dumpbin did not add or modify any of the tracked DLLs" -ForegroundColor Green +} else { + if ($addedDlls.Count -gt 0) { + Write-Host "Dumpbin installation added $($addedDlls.Count) DLL(s)" -ForegroundColor Yellow + + # Check if any added DLLs match ruby_bin versions + $identicalToRubyBin = 0 + $differentFromRubyBin = 0 + $notInRubyBin = 0 + + $dllsByName = @{} + foreach ($key in $postDlls.Keys) { + $dll = $postDlls[$key] + if (-not $dllsByName.ContainsKey($dll.Name)) { + $dllsByName[$dll.Name] = @() + } + $dllsByName[$dll.Name] += $dll + } + + foreach ($key in $addedDlls.Keys) { + $addedDll = $addedDlls[$key] + $rubyBinVersions = $dllsByName[$addedDll.Name] | Where-Object { + $_.Path -match 'ruby_bin.*AMD64' -and $_.Path -ne $addedDll.Path + } + + if ($rubyBinVersions) { + $isIdentical = $false + foreach ($rubyDll in $rubyBinVersions) { + if ($rubyDll.SHA256 -eq $addedDll.SHA256) { + $isIdentical = $true + break + } + } + + if ($isIdentical) { + $identicalToRubyBin++ + } else { + $differentFromRubyBin++ + } + } else { + $notInRubyBin++ + } + } + + if ($identicalToRubyBin -gt 0) { + Write-Host " - $identicalToRubyBin DLL(s) are IDENTICAL to ruby_bin\AMD64 versions" -ForegroundColor Green + } + if ($differentFromRubyBin -gt 0) { + Write-Host " - $differentFromRubyBin DLL(s) are DIFFERENT from ruby_bin\AMD64 versions" -ForegroundColor Red + } + if ($notInRubyBin -gt 0) { + Write-Host " - $notInRubyBin DLL(s) are NOT present in ruby_bin\AMD64 folder" -ForegroundColor Magenta + } + } + + if ($changedDlls.Count -gt 0) { + Write-Host "• Dumpbin installation modified $($changedDlls.Count) existing DLL(s)" -ForegroundColor Red + } +} + +Write-Host "" +Write-Host "=== Analysis Complete ===" -ForegroundColor Cyan diff --git a/compare-dlls.ps1 b/compare-dlls.ps1 new file mode 100644 index 0000000..dd7fd76 --- /dev/null +++ b/compare-dlls.ps1 @@ -0,0 +1,331 @@ +# Script to compare DLL state before and after dumpbin installation +# This script installs Chef from MSI, captures DLL checksums pre/post dumpbin installation + +param( + [Parameter(Mandatory=$true)] + [string]$MsiFile +) + +$ErrorActionPreference = "Stop" + +Write-Host "=== Chef DLL Comparison Script ===" -ForegroundColor Cyan +Write-Host "" + +# Validate MSI file exists +if (-not (Test-Path $MsiFile)) { + Write-Host "ERROR: MSI file not found: $MsiFile" -ForegroundColor Red + exit 1 +} + +$MsiFile = Resolve-Path $MsiFile +Write-Host "Using MSI file: $MsiFile" -ForegroundColor Green + +# Extract version from MSI filename +if ($MsiFile -match 'chef-(\d+\.\d+\.\d+)') { + $extractedVersion = $matches[1] + $imageTag = "dll-compare-$extractedVersion" +} else { + $extractedVersion = "unknown" + $imageTag = "dll-compare" +} + +Write-Host "Image tag: $imageTag" -ForegroundColor Yellow +Write-Host "" + +# Create shared directory if it doesn't exist +$sharedDir = Join-Path $PSScriptRoot "shared" +if (-not (Test-Path $sharedDir)) { + Write-Host "Creating shared directory..." -ForegroundColor Yellow + New-Item -ItemType Directory -Path $sharedDir | Out-Null +} + +# Clean up old comparison files +Write-Host "Cleaning old comparison files..." -ForegroundColor Yellow +Get-ChildItem $sharedDir -Filter "*dumpbin.txt" | Remove-Item -Force + +# Create Dockerfile for this comparison +$dockerfileContent = @' +# escape=` +FROM mcr.microsoft.com/windows/servercore:ltsc2022 + +# Set up PowerShell execution policy +SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] + +# Copy MSI file to container +COPY chef-installer.msi C:\chef-installer.msi + +# Install Chef from MSI +RUN Write-Host 'Installing Chef from MSI...'; ` + Start-Process msiexec.exe -ArgumentList '/i', 'C:\chef-installer.msi', '/qn', '/norestart' -Wait; ` + Remove-Item C:\chef-installer.msi -Force + +# Accept Chef license +ENV CHEF_LICENSE=accept-silent + +# Set working directory +WORKDIR C:\ + +CMD ["powershell"] +'@ + +$dockerfilePath = Join-Path $PSScriptRoot "Dockerfile.dll-compare" +Write-Host "Creating temporary Dockerfile: $dockerfilePath" -ForegroundColor Yellow +$dockerfileContent | Out-File -FilePath $dockerfilePath -Encoding ASCII + +# Copy MSI to temporary location +$tempMsi = Join-Path $PSScriptRoot "chef-installer.msi" +Write-Host "Copying MSI to temporary location..." -ForegroundColor Yellow +Copy-Item $MsiFile $tempMsi -Force + +# Build Docker image +Write-Host "`n=== Building Docker image ===" -ForegroundColor Green +docker build -f $dockerfilePath -t chef-dll-compare:$imageTag . + +if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to build Docker image" -ForegroundColor Red + Remove-Item $tempMsi -Force -ErrorAction SilentlyContinue + Remove-Item $dockerfilePath -Force -ErrorAction SilentlyContinue + exit 1 +} + +# Clean up temporary files +Remove-Item $tempMsi -Force +Remove-Item $dockerfilePath -Force + +# Run pre-dumpbin analysis +Write-Host "`n=== Running Pre-Dumpbin Analysis ===" -ForegroundColor Green +docker run --rm ` + -v "${PWD}\shared:C:\shared" ` + chef-dll-compare:$imageTag ` + powershell -Command { + Write-Host 'Verifying dumpbin is not installed...' + $dumpbinCheck = Get-Command dumpbin.exe -ErrorAction SilentlyContinue + if ($dumpbinCheck) { + Write-Host 'ERROR: dumpbin.exe is already available in PATH!' -ForegroundColor Red + Write-Host " Location: $($dumpbinCheck.Source)" + exit 1 + } + Write-Host ' Confirmed: dumpbin.exe is NOT in PATH' + Write-Host '' + + Write-Host 'Collecting DLL information before dumpbin installation...' + Write-Host '' + + $dllsToFind = @( + 'KERNEL32.dll', + 'VCRUNTIME140.dll', + 'api-ms-win-crt-runtime-l1-1-0.dll', + 'api-ms-win-crt-heap-l1-1-0.dll', + 'MSVCP140.dll', + 'mscoree.dll' + ) + + $output = "=== DLL Information BEFORE Dumpbin Installation ===`n" + $output += "Timestamp: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')`n" + $output += "Computer: $env:COMPUTERNAME`n" + $output += "Dumpbin Status: NOT INSTALLED (verified)`n`n" + + foreach ($dllName in $dllsToFind) { + Write-Host "Searching for $dllName..." + $foundDlls = Get-ChildItem -Path 'C:\' -Include $dllName -Recurse -ErrorAction SilentlyContinue + + $output += "=== $dllName ===`n" + + if ($foundDlls) { + foreach ($dll in $foundDlls) { + Write-Host " Found: $($dll.FullName)" + $output += "Path: $($dll.FullName)`n" + $output += "Size: $($dll.Length) bytes`n" + $output += "LastWriteTime: $($dll.LastWriteTime)`n" + + # Calculate checksums + try { + $md5 = (Get-FileHash -Path $dll.FullName -Algorithm MD5).Hash + $sha256 = (Get-FileHash -Path $dll.FullName -Algorithm SHA256).Hash + $output += "MD5: $md5`n" + $output += "SHA256: $sha256`n" + Write-Host " MD5: $md5" + Write-Host " SHA256: $sha256" + } catch { + $output += "Error calculating checksums: $($_.Exception.Message)`n" + Write-Host " Error calculating checksums: $($_.Exception.Message)" -ForegroundColor Red + } + + $output += "`n" + } + } else { + Write-Host " $dllName not found" + $output += "Not found`n`n" + } + } + + $outputFile = "C:\shared\predumpbin.txt" + Write-Host '' + Write-Host "Writing results to $outputFile" + $output | Out-File -FilePath $outputFile -Encoding UTF8 + Write-Host "Pre-dumpbin analysis complete!" + } + +if ($LASTEXITCODE -ne 0) { + Write-Host "Pre-dumpbin analysis failed" -ForegroundColor Red + exit 1 +} + +# Install dumpbin and run post-dumpbin analysis +Write-Host "`n=== Installing Dumpbin and Running Post-Dumpbin Analysis ===" -ForegroundColor Green +docker run --rm ` + -v "${PWD}\shared:C:\shared" ` + chef-dll-compare:$imageTag ` + powershell -Command { + Write-Host 'Installing Visual Studio Build Tools for dumpbin...' + Write-Host '' + + # Download and install VS Build Tools + Invoke-WebRequest -Uri 'https://aka.ms/vs/17/release/vs_buildtools.exe' -OutFile 'vs_buildtools.exe' + Write-Host 'Running VS Build Tools installer (this will take several minutes)...' + Start-Process vs_buildtools.exe -ArgumentList '--quiet', '--wait', '--norestart', '--nocache', '--installPath', "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\BuildTools\", '--add', 'Microsoft.VisualStudio.Workload.VCTools', '--includeRecommended' -Wait + Remove-Item vs_buildtools.exe -Force + + # Add dumpbin to PATH + $vsPath = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC' + + if (-not (Test-Path $vsPath)) { + Write-Host 'ERROR: Visual Studio MSVC path not found after installation!' -ForegroundColor Red + Write-Host " Expected path: $vsPath" + exit 1 + } + + $msvcVersion = (Get-ChildItem $vsPath | Sort-Object Name -Descending | Select-Object -First 1).Name + $dumpbinPath = Join-Path (Join-Path (Join-Path $vsPath $msvcVersion) 'bin\Hostx64') 'x64' + $dumpbinExe = Join-Path $dumpbinPath 'dumpbin.exe' + + Write-Host "Looking for dumpbin at: $dumpbinExe" + + if (-not (Test-Path $dumpbinExe)) { + Write-Host 'ERROR: dumpbin.exe NOT found at expected location!' -ForegroundColor Red + Write-Host " Expected: $dumpbinExe" + Write-Host " Checking alternate locations..." + $allDumpbins = Get-ChildItem -Path "${env:ProgramFiles(x86)}\Microsoft Visual Studio" -Include 'dumpbin.exe' -Recurse -ErrorAction SilentlyContinue + if ($allDumpbins) { + Write-Host " Found dumpbin.exe at:" + $allDumpbins | ForEach-Object { Write-Host " $($_.FullName)" } + } + exit 1 + } + + $env:PATH = $env:PATH + ';' + $dumpbinPath + Write-Host " Confirmed: dumpbin.exe exists at $dumpbinExe" + Write-Host " Added to PATH: $dumpbinPath" + Write-Host " Version: $(& $dumpbinExe 2>&1 | Select-Object -First 1)" + Write-Host '' + + Write-Host 'Collecting DLL information after dumpbin installation...' + Write-Host '' + + $dllsToFind = @( + 'KERNEL32.dll', + 'VCRUNTIME140.dll', + 'api-ms-win-crt-runtime-l1-1-0.dll', + 'api-ms-win-crt-heap-l1-1-0.dll', + 'MSVCP140.dll', + 'mscoree.dll' + ) + + $output = "=== DLL Information AFTER Dumpbin Installation ===`n" + $output += "Timestamp: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')`n" + $output += "Computer: $env:COMPUTERNAME`n" + $dumpbinInfo = Get-Command dumpbin.exe -ErrorAction SilentlyContinue + if ($dumpbinInfo) { + $output += "Dumpbin Status: INSTALLED (verified)`n" + $output += "Dumpbin Location: $($dumpbinInfo.Source)`n" + } else { + $output += "Dumpbin Status: NOT FOUND (unexpected!)`n" + } + $output += "`n" + + foreach ($dllName in $dllsToFind) { + Write-Host "Searching for $dllName..." + $foundDlls = Get-ChildItem -Path 'C:\' -Include $dllName -Recurse -ErrorAction SilentlyContinue + + $output += "=== $dllName ===`n" + + if ($foundDlls) { + foreach ($dll in $foundDlls) { + Write-Host " Found: $($dll.FullName)" + $output += "Path: $($dll.FullName)`n" + $output += "Size: $($dll.Length) bytes`n" + $output += "LastWriteTime: $($dll.LastWriteTime)`n" + + # Calculate checksums + try { + $md5 = (Get-FileHash -Path $dll.FullName -Algorithm MD5).Hash + $sha256 = (Get-FileHash -Path $dll.FullName -Algorithm SHA256).Hash + $output += "MD5: $md5`n" + $output += "SHA256: $sha256`n" + Write-Host " MD5: $md5" + Write-Host " SHA256: $sha256" + } catch { + $output += "Error calculating checksums: $($_.Exception.Message)`n" + Write-Host " Error calculating checksums: $($_.Exception.Message)" -ForegroundColor Red + } + + $output += "`n" + } + } else { + Write-Host " $dllName not found" + $output += "Not found`n`n" + } + } + + $outputFile = "C:\shared\postdumpbin.txt" + Write-Host '' + Write-Host "Writing results to $outputFile" + $output | Out-File -FilePath $outputFile -Encoding UTF8 + Write-Host "Post-dumpbin analysis complete!" + } + +if ($LASTEXITCODE -ne 0) { + Write-Host "Post-dumpbin analysis failed" -ForegroundColor Red + exit 1 +} + +# Display results +Write-Host "`n=== Comparison Complete ===" -ForegroundColor Cyan +Write-Host "" + +$preFile = Join-Path $sharedDir "predumpbin.txt" +$postFile = Join-Path $sharedDir "postdumpbin.txt" + +if (Test-Path $preFile) { + Write-Host "=== Pre-Dumpbin Results ===" -ForegroundColor Yellow + Get-Content $preFile + Write-Host "" +} + +if (Test-Path $postFile) { + Write-Host "=== Post-Dumpbin Results ===" -ForegroundColor Yellow + Get-Content $postFile + Write-Host "" +} + +# Compare files +Write-Host "=== Comparison Summary ===" -ForegroundColor Cyan + +if ((Test-Path $preFile) -and (Test-Path $postFile)) { + $preContent = Get-Content $preFile -Raw + $postContent = Get-Content $postFile -Raw + + if ($preContent -eq $postContent) { + Write-Host "No differences detected - DLL checksums are identical before and after dumpbin installation" -ForegroundColor Green + } else { + Write-Host "Differences detected - comparing files..." -ForegroundColor Yellow + Write-Host "" + Write-Host "You can manually compare the files:" -ForegroundColor White + Write-Host " Pre-dumpbin: $preFile" -ForegroundColor White + Write-Host " Post-dumpbin: $postFile" -ForegroundColor White + } +} else { + Write-Host "Could not perform comparison - one or both output files are missing" -ForegroundColor Red +} + +Write-Host "`n=== Script Complete ===" -ForegroundColor Cyan diff --git a/compare-gem-install-fixed.ps1 b/compare-gem-install-fixed.ps1 new file mode 100644 index 0000000..c2aad7c --- /dev/null +++ b/compare-gem-install-fixed.ps1 @@ -0,0 +1,332 @@ +# Simplified script to compare chef-powershell gem directory before and after gem install +param( + [string]$MsiFile = "omnibus-ruby_chef_pkg_chef-client-18.8.50-1-x64.msi", + [string]$GemVersion = "18.6.3" +) + +$ErrorActionPreference = "Stop" + +Write-Host "=== Chef PowerShell Gem Install Comparison Script ===" -ForegroundColor Cyan +Write-Host "MSI File: $MsiFile" -ForegroundColor Yellow +Write-Host "Gem Version: $GemVersion" -ForegroundColor Yellow +Write-Host "" + +# Verify MSI file exists +if (-not (Test-Path $MsiFile)) { + Write-Host "ERROR: MSI file not found: $MsiFile" -ForegroundColor Red + exit 1 +} + +# Create shared directory if it doesn't exist +$sharedDir = Join-Path $PSScriptRoot "shared" +if (-not (Test-Path $sharedDir)) { + Write-Host "Creating shared directory..." -ForegroundColor Yellow + New-Item -ItemType Directory -Path $sharedDir | Out-Null +} + +# Clean up any previous comparison files +Write-Host "Cleaning previous comparison files..." -ForegroundColor Yellow +Get-ChildItem $sharedDir -Filter "*gem-comparison*" | Remove-Item -Force -ErrorAction SilentlyContinue + +# Copy MSI to temporary location for Docker build +Write-Host "Preparing MSI for Docker build..." -ForegroundColor Yellow +$tempMsi = Join-Path $PSScriptRoot "chef-installer.msi" +Copy-Item $MsiFile $tempMsi -Force + +try { + # Clean up Docker environment before building + Write-Host "`n=== Cleaning Docker Environment ===" -ForegroundColor Green + Write-Host "Pruning Docker system (removing unused containers, networks, images)..." -ForegroundColor Yellow + docker system prune -f + + if ($LASTEXITCODE -ne 0) { + Write-Host "WARNING: Docker prune failed, continuing anyway..." -ForegroundColor Yellow + } + else { + Write-Host "Docker environment cleaned successfully" -ForegroundColor Green + } + + # Build Docker image with MSI install + Write-Host "`n=== Building Chef MSI Docker Image ===" -ForegroundColor Green + docker build -f Dockerfile.msi --build-arg INSTALL_DUMPBIN=False -t chef-gem-compare:latest . + + if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to build Chef MSI image" -ForegroundColor Red + exit 1 + } + + # Create PowerShell scripts for running inside container + $initialScript = @' +Write-Host "Checking chef-powershell gem directory after MSI install..." +$gemPath = "C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-*" +$chefPSDirectories = Get-ChildItem -Path $gemPath -Directory -ErrorAction SilentlyContinue + +$initialState = @{ + Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + State = "Post-MSI Install" + Directories = @() + TotalItems = 0 +} + +if ($chefPSDirectories) { + foreach ($dir in $chefPSDirectories) { + Write-Host "Found chef-powershell directory: $($dir.FullName)" + $items = Get-ChildItem -Path $dir.FullName -Recurse -Force | ForEach-Object { + @{ + Path = $_.FullName.Replace("C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\", "") + Type = if ($_.PSIsContainer) { "Directory" } else { "File" } + Size = if (-not $_.PSIsContainer) { $_.Length } else { 0 } + LastWriteTime = $_.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss') + } + } + $initialState.Directories += @{ + Path = $dir.FullName + Name = $dir.Name + Items = $items + ItemCount = $items.Count + } + $initialState.TotalItems += $items.Count + } +} else { + Write-Host "No chef-powershell directories found after MSI install" +} + +$outputPath = "C:\shared\gem-comparison-initial.json" +$initialState | ConvertTo-Json -Depth 10 | Out-File -FilePath $outputPath -Encoding UTF8 +Write-Host "Initial state saved to: $outputPath" +Write-Host "Total items found: $($initialState.TotalItems)" +'@ + + $finalScript = @" +Write-Host "Installing chef-powershell gem version $GemVersion..." +try { + & "C:\opscode\chef\embedded\bin\gem" install chef-powershell -v $GemVersion + Write-Host "Gem installation completed" +} catch { + Write-Host "Gem installation encountered an issue: `$(`$_.Exception.Message)" + Write-Host "Continuing with state capture..." +} + +Write-Host "Checking chef-powershell gem directory after gem install..." +`$gemPath = "C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-*" +`$chefPSDirectories = Get-ChildItem -Path `$gemPath -Directory -ErrorAction SilentlyContinue + +`$finalState = @{ + Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + State = "Post-Gem Install" + Directories = @() + TotalItems = 0 +} + +if (`$chefPSDirectories) { + foreach (`$dir in `$chefPSDirectories) { + Write-Host "Found chef-powershell directory: `$(`$dir.FullName)" + `$items = Get-ChildItem -Path `$dir.FullName -Recurse -Force | ForEach-Object { + @{ + Path = `$_.FullName.Replace("C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\", "") + Type = if (`$_.PSIsContainer) { "Directory" } else { "File" } + Size = if (-not `$_.PSIsContainer) { `$_.Length } else { 0 } + LastWriteTime = `$_.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss') + } + } + `$finalState.Directories += @{ + Path = `$dir.FullName + Name = `$dir.Name + Items = `$items + ItemCount = `$items.Count + } + `$finalState.TotalItems += `$items.Count + } +} else { + Write-Host "No chef-powershell directories found after gem install" +} + +`$outputPath = "C:\shared\gem-comparison-final.json" +`$finalState | ConvertTo-Json -Depth 10 | Out-File -FilePath `$outputPath -Encoding UTF8 +Write-Host "Final state saved to: `$outputPath" +Write-Host "Total items found: `$(`$finalState.TotalItems)" +"@ + + # Step 1: Capture initial state after MSI install + Write-Host "`n=== Capturing Initial State (Post-MSI Install) ===" -ForegroundColor Green + docker run --rm -v "${PWD}\shared:C:\shared" chef-gem-compare:latest powershell -Command $initialScript + + if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to capture initial state" -ForegroundColor Red + exit 1 + } + + # Step 2: Install gem and capture final state + Write-Host "`n=== Installing chef-powershell gem and capturing final state ===" -ForegroundColor Green + docker run --rm -v "${PWD}\shared:C:\shared" chef-gem-compare:latest powershell -Command $finalScript + + if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to install gem or capture final state" -ForegroundColor Red + exit 1 + } + + # Step 3: Generate comparison report + Write-Host "`n=== Generating Comparison Report ===" -ForegroundColor Green + + $initialFile = Join-Path $sharedDir "gem-comparison-initial.json" + $finalFile = Join-Path $sharedDir "gem-comparison-final.json" + + if ((Test-Path $initialFile) -and (Test-Path $finalFile)) { + $initialState = Get-Content $initialFile | ConvertFrom-Json + $finalState = Get-Content $finalFile | ConvertFrom-Json + + # Create comparison report + $reportPath = Join-Path $sharedDir "gem-comparison-report.md" + + $report = @" +# Chef PowerShell Gem Install Comparison Report + +**Generated:** $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') +**MSI File:** $MsiFile +**Gem Version:** $GemVersion + +## Summary + +- **Initial State (Post-MSI):** $($initialState.TotalItems) items found +- **Final State (Post-Gem Install):** $($finalState.TotalItems) items found +- **Difference:** $($finalState.TotalItems - $initialState.TotalItems) items + +## Initial State Directories + +"@ + + foreach ($dir in $initialState.Directories) { + $report += "`n### $($dir.Name)`n" + $report += "- **Path:** $($dir.Path)`n" + $report += "- **Item Count:** $($dir.ItemCount)`n" + } + + $report += "`n## Final State Directories`n" + + foreach ($dir in $finalState.Directories) { + $report += "`n### $($dir.Name)`n" + $report += "- **Path:** $($dir.Path)`n" + $report += "- **Item Count:** $($dir.ItemCount)`n" + } + + # Compare directories and files + $report += "`n## Detailed Comparison`n" + + # Create hashtables for easier comparison + $initialPaths = @{} + $finalPaths = @{} + + foreach ($dir in $initialState.Directories) { + foreach ($item in $dir.Items) { + $initialPaths[$item.Path] = $item + } + } + + foreach ($dir in $finalState.Directories) { + foreach ($item in $dir.Items) { + $finalPaths[$item.Path] = $item + } + } + + # Find new items + $newItems = @() + foreach ($path in $finalPaths.Keys) { + if (-not $initialPaths.ContainsKey($path)) { + $newItems += $finalPaths[$path] + } + } + + # Find removed items + $removedItems = @() + foreach ($path in $initialPaths.Keys) { + if (-not $finalPaths.ContainsKey($path)) { + $removedItems += $initialPaths[$path] + } + } + + # Find modified items (size or timestamp changes) + $modifiedItems = @() + foreach ($path in $initialPaths.Keys) { + if ($finalPaths.ContainsKey($path)) { + $initial = $initialPaths[$path] + $final = $finalPaths[$path] + if ($initial.Size -ne $final.Size -or $initial.LastWriteTime -ne $final.LastWriteTime) { + $modifiedItems += @{ + Path = $path + Initial = $initial + Final = $final + } + } + } + } + + if ($newItems.Count -gt 0) { + $report += "`n### New Items ($($newItems.Count))`n" + foreach ($item in $newItems | Sort-Object Path) { + $report += "- **$($item.Type):** $($item.Path)" + if ($item.Type -eq "File") { + $report += " ($($item.Size) bytes)" + } + $report += "`n" + } + } + else { + $report += "`n### New Items: None`n" + } + + if ($removedItems.Count -gt 0) { + $report += "`n### Removed Items ($($removedItems.Count))`n" + foreach ($item in $removedItems | Sort-Object Path) { + $report += "- **$($item.Type):** $($item.Path)" + if ($item.Type -eq "File") { + $report += " ($($item.Size) bytes)" + } + $report += "`n" + } + } + else { + $report += "`n### Removed Items: None`n" + } + + if ($modifiedItems.Count -gt 0) { + $report += "`n### Modified Items ($($modifiedItems.Count))`n" + foreach ($item in $modifiedItems | Sort-Object Path) { + $report += "- **File:** $($item.Path)`n" + $report += " - Initial: $($item.Initial.Size) bytes, $($item.Initial.LastWriteTime)`n" + $report += " - Final: $($item.Final.Size) bytes, $($item.Final.LastWriteTime)`n" + } + } + else { + $report += "`n### Modified Items: None`n" + } + + # Save report + $report | Out-File -FilePath $reportPath -Encoding UTF8 + + Write-Host "Comparison report generated: $reportPath" -ForegroundColor Green + Write-Host "" + Write-Host "=== Summary ===" -ForegroundColor Cyan + Write-Host "New items: $($newItems.Count)" -ForegroundColor Yellow + Write-Host "Removed items: $($removedItems.Count)" -ForegroundColor Yellow + Write-Host "Modified items: $($modifiedItems.Count)" -ForegroundColor Yellow + Write-Host "Total change: $($finalState.TotalItems - $initialState.TotalItems) items" -ForegroundColor Yellow + + } + else { + Write-Host "ERROR: Could not find comparison files" -ForegroundColor Red + exit 1 + } + +} +finally { + # Clean up temporary MSI file + if (Test-Path $tempMsi) { + Remove-Item $tempMsi -Force -ErrorAction SilentlyContinue + } +} + +Write-Host "`n=== Comparison Complete ===" -ForegroundColor Cyan +Write-Host "Check the shared/ directory for detailed results:" -ForegroundColor Green +Write-Host "- gem-comparison-initial.json - Initial state after MSI install" -ForegroundColor White +Write-Host "- gem-comparison-final.json - Final state after gem install" -ForegroundColor White +Write-Host "- gem-comparison-report.md - Human-readable comparison report" -ForegroundColor White \ No newline at end of file diff --git a/compare-gem-install.ps1 b/compare-gem-install.ps1 new file mode 100644 index 0000000..5ae79c9 --- /dev/null +++ b/compare-gem-install.ps1 @@ -0,0 +1,335 @@ +# Script to compare chef-powershell gem directory before and after gem install +param( + [string]$MsiFile = "omnibus-ruby_chef_pkg_chef-client-18.8.50-1-x64.msi", + [string]$GemVersion = "18.6.3" +) + +$ErrorActionPreference = "Stop" + +Write-Host "=== Chef PowerShell Gem Install Comparison Script ===" -ForegroundColor Cyan +Write-Host "MSI File: $MsiFile" -ForegroundColor Yellow +Write-Host "Gem Version: $GemVersion" -ForegroundColor Yellow +Write-Host "" + +# Verify MSI file exists +if (-not (Test-Path $MsiFile)) { + Write-Host "ERROR: MSI file not found: $MsiFile" -ForegroundColor Red + exit 1 +} + +# Create shared directory if it doesn't exist +$sharedDir = Join-Path $PSScriptRoot "shared" +if (-not (Test-Path $sharedDir)) { + Write-Host "Creating shared directory..." -ForegroundColor Yellow + New-Item -ItemType Directory -Path $sharedDir | Out-Null +} + +# Clean up any previous comparison files +Write-Host "Cleaning previous comparison files..." -ForegroundColor Yellow +Get-ChildItem $sharedDir -Filter "*gem-comparison*" | Remove-Item -Force -ErrorAction SilentlyContinue + +# Copy MSI to temporary location for Docker build +Write-Host "Preparing MSI for Docker build..." -ForegroundColor Yellow +$tempMsi = Join-Path $PSScriptRoot "chef-installer.msi" +Copy-Item $MsiFile $tempMsi -Force + +try { + # Build Docker image with MSI install + Write-Host "`n=== Building Chef MSI Docker Image ===" -ForegroundColor Green + docker build -f Dockerfile.msi --build-arg INSTALL_DUMPBIN=False -t chef-gem-compare:latest . + + if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to build Chef MSI image" -ForegroundColor Red + exit 1 + } + + # Step 1: Capture initial state after MSI install + Write-Host "`n=== Capturing Initial State (Post-MSI Install) ===" -ForegroundColor Green + docker run --rm -v "${PWD}\shared:C:\shared" chef-gem-compare:latest powershell -Command { + Write-Host "Checking chef-powershell gem directory after MSI install..." + + $gemPath = "C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-*" + $chefPSDirectories = Get-ChildItem -Path $gemPath -Directory -ErrorAction SilentlyContinue + + $initialState = @{ + Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + State = "Post-MSI Install" + Directories = @() + TotalItems = 0 + } + + if ($chefPSDirectories) { + foreach ($dir in $chefPSDirectories) { + Write-Host "Found chef-powershell directory: $($dir.FullName)" + + # Get all files and directories recursively + $items = Get-ChildItem -Path $dir.FullName -Recurse -Force | ForEach-Object { + @{ + Path = $_.FullName.Replace("C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\", "") + Type = if ($_.PSIsContainer) { "Directory" } else { "File" } + Size = if (-not $_.PSIsContainer) { $_.Length } else { 0 } + LastWriteTime = $_.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss') + } + } + + $initialState.Directories += @{ + Path = $dir.FullName + Name = $dir.Name + Items = $items + ItemCount = $items.Count + } + + $initialState.TotalItems += $items.Count + } + } + else { + Write-Host "No chef-powershell directories found after MSI install" + } + + # Save initial state to JSON + $outputPath = "C:\shared\gem-comparison-initial.json" + $initialState | ConvertTo-Json -Depth 10 | Out-File -FilePath $outputPath -Encoding UTF8 + Write-Host "Initial state saved to: $outputPath" + Write-Host "Total items found: $($initialState.TotalItems)" + } + + if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to capture initial state" -ForegroundColor Red + exit 1 + } + + # Step 2: Install gem and capture final state + Write-Host "`n=== Installing chef-powershell gem and capturing final state ===" -ForegroundColor Green + docker run --rm -v "${PWD}\shared:C:\shared" chef-gem-compare:latest powershell -Command { + param($gemVersion) + + Write-Host "Installing chef-powershell gem version $gemVersion..." + + # Install the gem + try { + & "C:\opscode\chef\embedded\bin\gem" install chef-powershell -v $gemVersion + Write-Host "Gem installation completed" + } + catch { + Write-Host "Gem installation encountered an issue: $($_.Exception.Message)" -ForegroundColor Yellow + Write-Host "Continuing with state capture..." + } + + Write-Host "Checking chef-powershell gem directory after gem install..." + + $gemPath = "C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-*" + $chefPSDirectories = Get-ChildItem -Path $gemPath -Directory -ErrorAction SilentlyContinue + + $finalState = @{ + Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + State = "Post-Gem Install" + Directories = @() + TotalItems = 0 + } + + if ($chefPSDirectories) { + foreach ($dir in $chefPSDirectories) { + Write-Host "Found chef-powershell directory: $($dir.FullName)" + + # Get all files and directories recursively + $items = Get-ChildItem -Path $dir.FullName -Recurse -Force | ForEach-Object { + @{ + Path = $_.FullName.Replace("C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\", "") + Type = if ($_.PSIsContainer) { "Directory" } else { "File" } + Size = if (-not $_.PSIsContainer) { $_.Length } else { 0 } + LastWriteTime = $_.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss') + } + } + + $finalState.Directories += @{ + Path = $dir.FullName + Name = $dir.Name + Items = $items + ItemCount = $items.Count + } + + $finalState.TotalItems += $items.Count + } + } + else { + Write-Host "No chef-powershell directories found after gem install" + } + + # Save final state to JSON + $outputPath = "C:\shared\gem-comparison-final.json" + $finalState | ConvertTo-Json -Depth 10 | Out-File -FilePath $outputPath -Encoding UTF8 + Write-Host "Final state saved to: $outputPath" + Write-Host "Total items found: $($finalState.TotalItems)" + + } -ArgumentList $GemVersion + + if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to install gem or capture final state" -ForegroundColor Red + exit 1 + } + + # Step 3: Generate comparison report + Write-Host "`n=== Generating Comparison Report ===" -ForegroundColor Green + + $initialFile = Join-Path $sharedDir "gem-comparison-initial.json" + $finalFile = Join-Path $sharedDir "gem-comparison-final.json" + + if ((Test-Path $initialFile) -and (Test-Path $finalFile)) { + $initialState = Get-Content $initialFile | ConvertFrom-Json + $finalState = Get-Content $finalFile | ConvertFrom-Json + + # Create comparison report + $reportPath = Join-Path $sharedDir "gem-comparison-report.md" + + $report = @" +# Chef PowerShell Gem Install Comparison Report + +**Generated:** $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') +**MSI File:** $MsiFile +**Gem Version:** $GemVersion + +## Summary + +- **Initial State (Post-MSI):** $($initialState.TotalItems) items found +- **Final State (Post-Gem Install):** $($finalState.TotalItems) items found +- **Difference:** $($finalState.TotalItems - $initialState.TotalItems) items + +## Initial State Directories + +"@ + + foreach ($dir in $initialState.Directories) { + $report += "`n### $($dir.Name)`n" + $report += "- **Path:** $($dir.Path)`n" + $report += "- **Item Count:** $($dir.ItemCount)`n" + } + + $report += "`n## Final State Directories`n" + + foreach ($dir in $finalState.Directories) { + $report += "`n### $($dir.Name)`n" + $report += "- **Path:** $($dir.Path)`n" + $report += "- **Item Count:** $($dir.ItemCount)`n" + } + + # Compare directories and files + $report += "`n## Detailed Comparison`n" + + # Create hashtables for easier comparison + $initialPaths = @{} + $finalPaths = @{} + + foreach ($dir in $initialState.Directories) { + foreach ($item in $dir.Items) { + $initialPaths[$item.Path] = $item + } + } + + foreach ($dir in $finalState.Directories) { + foreach ($item in $dir.Items) { + $finalPaths[$item.Path] = $item + } + } + + # Find new items + $newItems = @() + foreach ($path in $finalPaths.Keys) { + if (-not $initialPaths.ContainsKey($path)) { + $newItems += $finalPaths[$path] + } + } + + # Find removed items + $removedItems = @() + foreach ($path in $initialPaths.Keys) { + if (-not $finalPaths.ContainsKey($path)) { + $removedItems += $initialPaths[$path] + } + } + + # Find modified items (size or timestamp changes) + $modifiedItems = @() + foreach ($path in $initialPaths.Keys) { + if ($finalPaths.ContainsKey($path)) { + $initial = $initialPaths[$path] + $final = $finalPaths[$path] + if ($initial.Size -ne $final.Size -or $initial.LastWriteTime -ne $final.LastWriteTime) { + $modifiedItems += @{ + Path = $path + Initial = $initial + Final = $final + } + } + } + } + + if ($newItems.Count -gt 0) { + $report += "`n### New Items ($($newItems.Count))`n" + foreach ($item in $newItems) { + $report += "- **$($item.Type):** $($item.Path)" + if ($item.Type -eq "File") { + $report += " ($($item.Size) bytes)" + } + $report += "`n" + } + } + else { + $report += "`n### New Items: None`n" + } + + if ($removedItems.Count -gt 0) { + $report += "`n### Removed Items ($($removedItems.Count))`n" + foreach ($item in $removedItems) { + $report += "- **$($item.Type):** $($item.Path)" + if ($item.Type -eq "File") { + $report += " ($($item.Size) bytes)" + } + $report += "`n" + } + } + else { + $report += "`n### Removed Items: None`n" + } + + if ($modifiedItems.Count -gt 0) { + $report += "`n### Modified Items ($($modifiedItems.Count))`n" + foreach ($item in $modifiedItems) { + $report += "- **File:** $($item.Path)`n" + $report += " - Initial: $($item.Initial.Size) bytes, $($item.Initial.LastWriteTime)`n" + $report += " - Final: $($item.Final.Size) bytes, $($item.Final.LastWriteTime)`n" + } + } + else { + $report += "`n### Modified Items: None`n" + } + + # Save report + $report | Out-File -FilePath $reportPath -Encoding UTF8 + + Write-Host "Comparison report generated: $reportPath" -ForegroundColor Green + Write-Host "" + Write-Host "=== Summary ===" -ForegroundColor Cyan + Write-Host "New items: $($newItems.Count)" -ForegroundColor Yellow + Write-Host "Removed items: $($removedItems.Count)" -ForegroundColor Yellow + Write-Host "Modified items: $($modifiedItems.Count)" -ForegroundColor Yellow + Write-Host "Total change: $($finalState.TotalItems - $initialState.TotalItems) items" -ForegroundColor Yellow + + } + else { + Write-Host "ERROR: Could not find comparison files" -ForegroundColor Red + exit 1 + } + +} +finally { + # Clean up temporary MSI file + if (Test-Path $tempMsi) { + Remove-Item $tempMsi -Force -ErrorAction SilentlyContinue + } +} + +Write-Host "`n=== Comparison Complete ===" -ForegroundColor Cyan +Write-Host "Check the shared/ directory for detailed results:" -ForegroundColor Green +Write-Host "- gem-comparison-initial.json - Initial state after MSI install" -ForegroundColor White +Write-Host "- gem-comparison-final.json - Final state after gem install" -ForegroundColor White +Write-Host "- gem-comparison-report.md - Human-readable comparison report" -ForegroundColor White \ No newline at end of file diff --git a/compare-gem-pristine.ps1 b/compare-gem-pristine.ps1 new file mode 100644 index 0000000..25c7e8c --- /dev/null +++ b/compare-gem-pristine.ps1 @@ -0,0 +1,417 @@ +# Enhanced script to compare chef-powershell gem directory and test PowerShell functionality before and after gem pristine +param( + [string]$MsiFile = "omnibus-ruby_chef_pkg_chef-client-18.8.50-1-x64.msi", + [string]$GemVersion = "18.6.3" +) + +$ErrorActionPreference = "Stop" + +# Track if any chef runs failed +$script:chefRunFailed = $false + +Write-Host "=== Enhanced Chef PowerShell Gem Pristine Comparison Script ===" -ForegroundColor Cyan +Write-Host "MSI File: $MsiFile" -ForegroundColor Yellow +Write-Host "Gem Version: $GemVersion" -ForegroundColor Yellow +Write-Host "" + +# Verify MSI file exists +if (-not (Test-Path $MsiFile)) { + Write-Host "ERROR: MSI file not found: $MsiFile" -ForegroundColor Red + exit 1 +} + +# Create shared directory if it doesn't exist +$sharedDir = Join-Path $PSScriptRoot "shared" +if (-not (Test-Path $sharedDir)) { + Write-Host "Creating shared directory..." -ForegroundColor Yellow + New-Item -ItemType Directory -Path $sharedDir | Out-Null +} + +# Clean up any previous comparison files +Write-Host "Cleaning previous comparison files..." -ForegroundColor Yellow +Get-ChildItem $sharedDir -Filter "*gem-pristine*" | Remove-Item -Force -ErrorAction SilentlyContinue + +# Copy MSI to temporary location for Docker build +Write-Host "Preparing MSI for Docker build..." -ForegroundColor Yellow +$tempMsi = Join-Path $PSScriptRoot "chef-installer.msi" +Copy-Item $MsiFile $tempMsi -Force + +try { + # Clean up Docker environment before building + Write-Host "`n=== Cleaning Docker Environment ===" -ForegroundColor Green + Write-Host "Pruning Docker system (removing unused containers, networks, images)..." -ForegroundColor Yellow + docker system prune -f + + if ($LASTEXITCODE -ne 0) { + Write-Host "WARNING: Docker prune failed, continuing anyway..." -ForegroundColor Yellow + } + else { + Write-Host "Docker environment cleaned successfully" -ForegroundColor Green + } + + # Build Docker image with MSI install + Write-Host "`n=== Building Chef MSI Docker Image ===" -ForegroundColor Green + docker build -f Dockerfile.msi --build-arg INSTALL_DUMPBIN=False -t chef-gem-pristine:latest . + + if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to build Chef MSI image" -ForegroundColor Red + exit 1 + } + + # Create PowerShell scripts for running inside container + $initialScript = @' +Write-Host "=== Initial State Analysis (Post-MSI Install) ===" +Write-Host "Checking chef-powershell gem directory after MSI install..." + +$gemPath = "C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-*" +$chefPSDirectories = Get-ChildItem -Path $gemPath -Directory -ErrorAction SilentlyContinue + +$initialState = @{ + Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + State = "Post-MSI Install" + Directories = @() + TotalItems = 0 + ChefRunSuccess = $false + ChefRunError = "" +} + +if ($chefPSDirectories) { + foreach ($dir in $chefPSDirectories) { + Write-Host "Found chef-powershell directory: $($dir.FullName)" + $items = Get-ChildItem -Path $dir.FullName -Recurse -Force | ForEach-Object { + @{ + Path = $_.FullName.Replace("C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\", "") + Type = if ($_.PSIsContainer) { "Directory" } else { "File" } + Size = if (-not $_.PSIsContainer) { $_.Length } else { 0 } + LastWriteTime = $_.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss') + } + } + $initialState.Directories += @{ + Path = $dir.FullName + Name = $dir.Name + Items = $items + ItemCount = $items.Count + } + $initialState.TotalItems += $items.Count + } +} else { + Write-Host "No chef-powershell directories found after MSI install" +} + +Write-Host "" +Write-Host "=== Testing PowerShell Recipe (Pre-Gem Pristine) ===" + +# Test Chef PowerShell functionality by running the recipe +$env:PATH += ";C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-18.1.0\bin\ruby_bin_folder\AMD64;C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-18.1.0\bin\ruby_bin_folder\AMD64\shared\Microsoft.NETCore.App\5.0.0" + +try { + Write-Host "Running chef-client with test recipe..." + $chefOutput = & chef-client -z -o recipe[test_recipe] --chef-license accept-silent 2>&1 + Write-Host "Chef run completed successfully (pre-pristine)" + $initialState.ChefRunSuccess = $true +} catch { + Write-Host "Chef run failed (pre-pristine): $($_.Exception.Message)" -ForegroundColor Yellow + $initialState.ChefRunSuccess = $false + $initialState.ChefRunError = $_.Exception.Message +} + +$outputPath = "C:\shared\gem-pristine-initial.json" +$initialState | ConvertTo-Json -Depth 10 | Out-File -FilePath $outputPath -Encoding UTF8 +Write-Host "Initial state saved to: $outputPath" +Write-Host "Total items found: $($initialState.TotalItems)" +Write-Host "Chef run success: $($initialState.ChefRunSuccess)" +'@ + + $finalScript = @" +Write-Host "=== Gem Pristine and Final State Analysis ===" +Write-Host "Running gem pristine for chef-powershell..." + +# Run gem pristine to reinstall the gem +try { + & "C:\opscode\chef\embedded\bin\gem" pristine chef-powershell + Write-Host "Gem pristine completed successfully" +} catch { + Write-Host "Gem pristine encountered an issue: `$(`$_.Exception.Message)" -ForegroundColor Yellow + Write-Host "Continuing with state capture..." +} + +Write-Host "" +Write-Host "Checking chef-powershell gem directory after gem pristine..." + +`$gemPath = "C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-*" +`$chefPSDirectories = Get-ChildItem -Path `$gemPath -Directory -ErrorAction SilentlyContinue + +`$finalState = @{ + Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + State = "Post-Gem Pristine" + Directories = @() + TotalItems = 0 + ChefRunSuccess = `$false + ChefRunError = "" +} + +if (`$chefPSDirectories) { + foreach (`$dir in `$chefPSDirectories) { + Write-Host "Found chef-powershell directory: `$(`$dir.FullName)" + `$items = Get-ChildItem -Path `$dir.FullName -Recurse -Force | ForEach-Object { + @{ + Path = `$_.FullName.Replace("C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\", "") + Type = if (`$_.PSIsContainer) { "Directory" } else { "File" } + Size = if (-not `$_.PSIsContainer) { `$_.Length } else { 0 } + LastWriteTime = `$_.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss') + } + } + `$finalState.Directories += @{ + Path = `$dir.FullName + Name = `$dir.Name + Items = `$items + ItemCount = `$items.Count + } + `$finalState.TotalItems += `$items.Count + } +} else { + Write-Host "No chef-powershell directories found after gem pristine" +} + +Write-Host "" +Write-Host "=== Testing PowerShell Recipe (Post-Gem Pristine) ===" + +# Test Chef PowerShell functionality by running the recipe +`$env:PATH += ";C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-18.1.0\bin\ruby_bin_folder\AMD64;C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-18.1.0\bin\ruby_bin_folder\AMD64\shared\Microsoft.NETCore.App\5.0.0" + +try { + Write-Host "Running chef-client with test recipe..." + `$chefOutput = & chef-client -z -o recipe[test_recipe] --chef-license accept-silent 2>&1 + Write-Host "Chef run completed successfully (post-pristine)" + `$finalState.ChefRunSuccess = `$true +} catch { + Write-Host "Chef run failed (post-pristine): `$(`$_.Exception.Message)" -ForegroundColor Yellow + `$finalState.ChefRunSuccess = `$false + `$finalState.ChefRunError = `$_.Exception.Message +} + +`$outputPath = "C:\shared\gem-pristine-final.json" +`$finalState | ConvertTo-Json -Depth 10 | Out-File -FilePath `$outputPath -Encoding UTF8 +Write-Host "Final state saved to: `$outputPath" +Write-Host "Total items found: `$(`$finalState.TotalItems)" +Write-Host "Chef run success: `$(`$finalState.ChefRunSuccess)" +"@ + + # Step 1: Capture initial state and test Chef run + Write-Host "`n=== Capturing Initial State and Testing PowerShell Recipe ===" -ForegroundColor Green + docker run --rm -e CHEF_LICENSE=accept-silent -v "${PWD}\shared:C:\shared" -v "${PWD}\cookbooks:C:\cookbooks" chef-gem-pristine:latest powershell -Command $initialScript + + if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to capture initial state or run initial Chef test" -ForegroundColor Yellow + $script:chefRunFailed = $true + } + + # Step 2: Run gem pristine, capture final state, and test Chef run + Write-Host "`n=== Running Gem Pristine and Testing PowerShell Recipe ===" -ForegroundColor Green + docker run --rm -e CHEF_LICENSE=accept-silent -v "${PWD}\shared:C:\shared" -v "${PWD}\cookbooks:C:\cookbooks" chef-gem-pristine:latest powershell -Command $finalScript + + if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to run gem pristine or capture final state" -ForegroundColor Yellow + $script:chefRunFailed = $true + } + + # Step 3: Generate comparison report + Write-Host "`n=== Generating Comprehensive Comparison Report ===" -ForegroundColor Green + + $initialFile = Join-Path $sharedDir "gem-pristine-initial.json" + $finalFile = Join-Path $sharedDir "gem-pristine-final.json" + + if ((Test-Path $initialFile) -and (Test-Path $finalFile)) { + $initialState = Get-Content $initialFile | ConvertFrom-Json + $finalState = Get-Content $finalFile | ConvertFrom-Json + + # Create comparison report + $reportPath = Join-Path $sharedDir "gem-pristine-report.md" + + $report = @" +# Chef PowerShell Gem Pristine Comparison Report + +**Generated:** $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') +**MSI File:** $MsiFile +**Operation:** gem pristine chef-powershell + +## Summary + +- **Initial State (Post-MSI):** $($initialState.TotalItems) items found +- **Final State (Post-Gem Pristine):** $($finalState.TotalItems) items found +- **File/Directory Difference:** $($finalState.TotalItems - $initialState.TotalItems) items + +## Chef PowerShell Recipe Test Results + +### Pre-Gem Pristine Test +- **Success:** $($initialState.ChefRunSuccess) +- **Error:** $($initialState.ChefRunError) + +### Post-Gem Pristine Test +- **Success:** $($finalState.ChefRunSuccess) +- **Error:** $($finalState.ChefRunError) + +## Initial State Directories + +"@ + + foreach ($dir in $initialState.Directories) { + $report += "`n### $($dir.Name)`n" + $report += "- **Path:** $($dir.Path)`n" + $report += "- **Item Count:** $($dir.ItemCount)`n" + } + + $report += "`n## Final State Directories`n" + + foreach ($dir in $finalState.Directories) { + $report += "`n### $($dir.Name)`n" + $report += "- **Path:** $($dir.Path)`n" + $report += "- **Item Count:** $($dir.ItemCount)`n" + } + + # Compare directories and files + $report += "`n## Detailed File System Comparison`n" + + # Create hashtables for easier comparison + $initialPaths = @{} + $finalPaths = @{} + + foreach ($dir in $initialState.Directories) { + foreach ($item in $dir.Items) { + $initialPaths[$item.Path] = $item + } + } + + foreach ($dir in $finalState.Directories) { + foreach ($item in $dir.Items) { + $finalPaths[$item.Path] = $item + } + } + + # Find new items + $newItems = @() + foreach ($path in $finalPaths.Keys) { + if (-not $initialPaths.ContainsKey($path)) { + $newItems += $finalPaths[$path] + } + } + + # Find removed items + $removedItems = @() + foreach ($path in $initialPaths.Keys) { + if (-not $finalPaths.ContainsKey($path)) { + $removedItems += $initialPaths[$path] + } + } + + # Find modified items (size or timestamp changes) + $modifiedItems = @() + foreach ($path in $initialPaths.Keys) { + if ($finalPaths.ContainsKey($path)) { + $initial = $initialPaths[$path] + $final = $finalPaths[$path] + if ($initial.Size -ne $final.Size -or $initial.LastWriteTime -ne $final.LastWriteTime) { + $modifiedItems += @{ + Path = $path + Initial = $initial + Final = $final + } + } + } + } + + if ($newItems.Count -gt 0) { + $report += "`n### New Items ($($newItems.Count))`n" + foreach ($item in $newItems | Sort-Object Path) { + $report += "- **$($item.Type):** $($item.Path)" + if ($item.Type -eq "File") { + $report += " ($($item.Size) bytes)" + } + $report += "`n" + } + } + else { + $report += "`n### New Items: None`n" + } + + if ($removedItems.Count -gt 0) { + $report += "`n### Removed Items ($($removedItems.Count))`n" + foreach ($item in $removedItems | Sort-Object Path) { + $report += "- **$($item.Type):** $($item.Path)" + if ($item.Type -eq "File") { + $report += " ($($item.Size) bytes)" + } + $report += "`n" + } + } + else { + $report += "`n### Removed Items: None`n" + } + + if ($modifiedItems.Count -gt 0) { + $report += "`n### Modified Items ($($modifiedItems.Count))`n" + foreach ($item in $modifiedItems | Sort-Object Path) { + $report += "- **File:** $($item.Path)`n" + $report += " - Initial: $($item.Initial.Size) bytes, $($item.Initial.LastWriteTime)`n" + $report += " - Final: $($item.Final.Size) bytes, $($item.Final.LastWriteTime)`n" + } + } + else { + $report += "`n### Modified Items: None`n" + } + + # Add functionality assessment + $report += "`n## Chef PowerShell Functionality Assessment`n" + + if ($initialState.ChefRunSuccess -and $finalState.ChefRunSuccess) { + $report += "✅ **PASS** - PowerShell recipes work both before and after gem pristine`n" + } + elseif (-not $initialState.ChefRunSuccess -and $finalState.ChefRunSuccess) { + $report += "✅ **IMPROVED** - PowerShell recipes now work after gem pristine (were broken before)`n" + } + elseif ($initialState.ChefRunSuccess -and -not $finalState.ChefRunSuccess) { + $report += "❌ **REGRESSION** - PowerShell recipes worked before but are broken after gem pristine`n" + } + else { + $report += "❌ **FAIL** - PowerShell recipes don't work before or after gem pristine`n" + } + + # Save report + $report | Out-File -FilePath $reportPath -Encoding UTF8 + + Write-Host "Comparison report generated: $reportPath" -ForegroundColor Green + Write-Host "" + Write-Host "=== Summary ===" -ForegroundColor Cyan + Write-Host "New items: $($newItems.Count)" -ForegroundColor Yellow + Write-Host "Removed items: $($removedItems.Count)" -ForegroundColor Yellow + Write-Host "Modified items: $($modifiedItems.Count)" -ForegroundColor Yellow + Write-Host "Total change: $($finalState.TotalItems - $initialState.TotalItems) items" -ForegroundColor Yellow + Write-Host "Pre-pristine Chef run: $(if ($initialState.ChefRunSuccess) { 'SUCCESS' } else { 'FAILED' })" -ForegroundColor $(if ($initialState.ChefRunSuccess) { 'Green' } else { 'Red' }) + Write-Host "Post-pristine Chef run: $(if ($finalState.ChefRunSuccess) { 'SUCCESS' } else { 'FAILED' })" -ForegroundColor $(if ($finalState.ChefRunSuccess) { 'Green' } else { 'Red' }) + + } + else { + Write-Host "ERROR: Could not find comparison files" -ForegroundColor Red + exit 1 + } + +} +finally { + # Clean up temporary MSI file + if (Test-Path $tempMsi) { + Remove-Item $tempMsi -Force -ErrorAction SilentlyContinue + } +} + +Write-Host "`n=== Enhanced Comparison Complete ===" -ForegroundColor Cyan +Write-Host "Check the shared/ directory for detailed results:" -ForegroundColor Green +Write-Host "- gem-pristine-initial.json - Initial state and Chef test results" -ForegroundColor White +Write-Host "- gem-pristine-final.json - Final state and Chef test results" -ForegroundColor White +Write-Host "- gem-pristine-report.md - Comprehensive comparison report" -ForegroundColor White + +# Check if any chef runs failed and report +if ($script:chefRunFailed) { + Write-Host "`nWARNING: One or more operations encountered errors!" -ForegroundColor Red + Write-Host "However, comparison and reporting completed. Check the report for details." -ForegroundColor Yellow + exit 1 +} \ No newline at end of file diff --git a/cookbooks/test_recipe/recipes/default.rb b/cookbooks/test_recipe/recipes/default.rb index e5414ed..e660b4a 100644 --- a/cookbooks/test_recipe/recipes/default.rb +++ b/cookbooks/test_recipe/recipes/default.rb @@ -1,53 +1,50 @@ -# Test recipe to output Chef version to file +# Test recipe to output Chef version to file with timestamp chef_version = Chef::VERSION -output_file = "C:\\shared\\chef-#{chef_version}.txt" +timestamp = Time.now.strftime("%Y%m%d-%H%M%S") +output_file = "C:\\shared\\chef-#{chef_version}-#{timestamp}.txt" log "Chef version: #{chef_version}" do level :info end -# Execute PowerShell script to output Chef version and diagnostic information -powershell_script 'output_chef_version' do - code <<-EOH - $chefVersion = "#{chef_version}" - $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" - $computerName = $env:COMPUTERNAME - - # Search for Chef.PowerShell.Wrapper.dll - Write-Host "Searching for Chef.PowerShell.Wrapper.dll during Chef run..." - $wrapperDlls = Get-ChildItem -Path "C:\\opscode\\chef" -Include "Chef.PowerShell.Wrapper.dll" -Recurse -ErrorAction SilentlyContinue - - $wrapperInfo = if ($wrapperDlls) { - $wrapperDlls | ForEach-Object { - " Path: $($_.FullName)`n Size: $($_.Length) bytes`n LastWriteTime: $($_.LastWriteTime)" - } | Out-String - } else { - " Chef.PowerShell.Wrapper.dll not found" - } - - $outputFile = "C:\\shared\\chef-$chefVersion.txt" - - # Append to existing file or create new one - $separator = "`n" + "=" * 70 + "`n" - $additionalOutput = @" -$separator -Chef Recipe Execution: -Timestamp: $timestamp -Computer Name: $computerName - -Chef.PowerShell.Wrapper.dll Search Results (During Chef Run): -$wrapperInfo -"@ - - Write-Host "Appending Chef run results to $outputFile" - $additionalOutput | Out-File -FilePath $outputFile -Encoding UTF8 -Append - Write-Host "Successfully appended Chef run information" - Get-Content $outputFile - EOH - action :run +log "Creating timestamped output file: #{output_file}" do + level :info +end + +# Use Ruby file I/O to create timestamped output file +# This approach avoids PowerShell DLL dependency issues +file output_file do + content <<-EOL +====================================================================== +Chef Recipe Execution Report +====================================================================== +Chef Version: #{chef_version} +Ruby Timestamp: #{timestamp} +Execution Time: #{Time.now.strftime("%Y-%m-%d %H:%M:%S")} +Recipe Status: SUCCESS +Output File: #{output_file} +====================================================================== + +This file was created by the Chef test recipe to verify: +1. Chef Ruby integration is working +2. File I/O operations are successful +3. Recipe execution completed without errors +4. Timestamp functionality is operational +5. Timestamped file naming is working + +Recipe Details: +- Used Ruby file resource instead of PowerShell script +- Avoids Chef PowerShell wrapper DLL dependencies +- Creates uniquely timestamped files for each run +- Demonstrates successful Chef recipe execution + +Generated at: #{Time.now.strftime("%Y-%m-%d %H:%M:%S")} +====================================================================== +EOL + action :create end -log "Chef version information written to #{output_file}" do +log "Chef recipe completed for version #{chef_version}" do level :info end diff --git a/cookbooks/test_recipe/recipes/ruby_only.rb b/cookbooks/test_recipe/recipes/ruby_only.rb new file mode 100644 index 0000000..be83580 --- /dev/null +++ b/cookbooks/test_recipe/recipes/ruby_only.rb @@ -0,0 +1,41 @@ +# Test recipe to output Chef version to file (Ruby-only version) + +chef_version = Chef::VERSION +timestamp = Time.now.strftime("%Y%m%d-%H%M%S") +output_file = "C:\\shared\\chef-#{chef_version}-#{timestamp}.txt" + +log "Chef version: #{chef_version}" do + level :info +end + +log "Creating output file: #{output_file}" do + level :info +end + +# Use Ruby file I/O instead of PowerShell to avoid DLL issues +file output_file do + content <<-EOL +====================================================================== +Chef Recipe Execution Report +====================================================================== +Chef Version: #{chef_version} +Ruby Timestamp: #{timestamp} +Recipe Status: SUCCESS +Output File: #{output_file} +====================================================================== + +This file was created by the Chef test recipe to verify: +1. Chef Ruby integration is working +2. File I/O operations are successful +3. Recipe execution completed without errors +4. Timestamp functionality is operational + +Generated at: #{Time.now.strftime("%Y-%m-%d %H:%M:%S")} +====================================================================== +EOL + action :create +end + +log "Chef recipe completed for version #{chef_version}" do + level :info +end \ No newline at end of file diff --git a/gem_install.ps1 b/gem_install.ps1 new file mode 100644 index 0000000..e69de29 diff --git a/run-test.ps1 b/run-test.ps1 index 4fc970f..e1a39b1 100644 --- a/run-test.ps1 +++ b/run-test.ps1 @@ -1,15 +1,113 @@ -# Script to build, run, and test Chef Docker containers # This script creates Windows containers with different Chef versions and tests them param( + # Script to build, run, and test Chef Docker containers [string]$ChefVersion1 = "18.8.11", - [string]$ChefVersion2 = "18.8.46" + [string]$ChefVersion2 = "18.8.46", + [string]$MsiFile = "", + [switch]$SingleVersion, + [switch]$FindDLLs, + [switch]$UseDumpbin ) $ErrorActionPreference = "Stop" +# Track if any chef runs failed +$script:chefRunFailed = $false + +# Helper function to play sounds +function Play-CompletionSound { + param([bool]$Success) + + if ($Success) { + # Success sound: Three ascending beeps + [Console]::Beep(800, 150) + Start-Sleep -Milliseconds 50 + [Console]::Beep(1000, 150) + Start-Sleep -Milliseconds 50 + [Console]::Beep(1200, 200) + } + else { + # Failure sound: Two descending beeps + [Console]::Beep(800, 200) + Start-Sleep -Milliseconds 100 + [Console]::Beep(400, 300) + } +} + +# Helper function to get DLL list from dumpbin output or use default list +function Get-DllsToFind { + param([string]$DumpbinOutput) + + $defaultDlls = @( + 'KERNEL32.dll', + 'VCRUNTIME140.dll', + 'api-ms-win-crt-runtime-l1-1-0.dll', + 'api-ms-win-crt-heap-l1-1-0.dll', + 'MSVCP140.dll', + 'mscoree.dll' + ) + + if ([string]::IsNullOrWhiteSpace($DumpbinOutput)) { + Write-Host ' Using default DLL list (dumpbin output not available)' + return $defaultDlls + } + + # Parse dumpbin output to extract DLL dependencies + Write-Host ' Parsing dumpbin output for DLL dependencies...' + $dllPattern = '^\s+([a-zA-Z0-9\-\.]+\.dll)' + $extractedDlls = @() + + $DumpbinOutput -split "`n" | ForEach-Object { + if ($_ -match $dllPattern) { + $dllName = $matches[1].Trim() + if ($dllName -and $extractedDlls -notcontains $dllName) { + $extractedDlls += $dllName + Write-Host " Found dependency: $dllName" + } + } + } + + if ($extractedDlls.Count -gt 0) { + Write-Host " Extracted $($extractedDlls.Count) DLL(s) from dumpbin output" + return $extractedDlls + } + else { + Write-Host ' No DLLs found in dumpbin output, using default list' + return $defaultDlls + } +} + Write-Host "=== Chef Docker Container Test Script ===" -ForegroundColor Cyan -Write-Host "Testing Chef versions: $ChefVersion1 vs $ChefVersion2" -ForegroundColor Cyan + +# Determine test mode +$versionsToTest = @() +$useMsi = $false + +if ($MsiFile -ne "") { + # MSI file mode + if (-not (Test-Path $MsiFile)) { + Write-Host "ERROR: MSI file not found: $MsiFile" -ForegroundColor Red + Play-CompletionSound -Success $false + exit 1 + } + $useMsi = $true + $MsiFile = Resolve-Path $MsiFile + Write-Host "Testing with MSI file: $MsiFile" -ForegroundColor Cyan + $versionsToTest += @{ Version = "msi"; MsiPath = $MsiFile } +} +elseif ($SingleVersion) { + # Single version mode + Write-Host "Testing single Chef version: $ChefVersion1" -ForegroundColor Cyan + $versionsToTest += @{ Version = $ChefVersion1; MsiPath = "" } +} +else { + # Two version comparison mode (default) + Write-Host "Testing Chef versions: $ChefVersion1 vs $ChefVersion2" -ForegroundColor Cyan + $versionsToTest += @{ Version = $ChefVersion1; MsiPath = "" } + $versionsToTest += @{ Version = $ChefVersion2; MsiPath = "" } +} + Write-Host "" # Create shared directory if it doesn't exist @@ -23,130 +121,397 @@ if (-not (Test-Path $sharedDir)) { Write-Host "Cleaning shared directory..." -ForegroundColor Yellow Get-ChildItem $sharedDir -Filter "chef-*.txt" | Remove-Item -Force -# Build and run first Chef version -Write-Host "`n=== Building Chef $ChefVersion1 container ===" -ForegroundColor Green -docker build --build-arg CHEF_VERSION=$ChefVersion1 -t chef-test:$ChefVersion1 . +# Clean up Docker environment before testing +Write-Host "`n=== Cleaning Docker Environment ===" -ForegroundColor Green +Write-Host "Pruning Docker system (removing unused containers, networks, images)..." -ForegroundColor Yellow +docker system prune -f if ($LASTEXITCODE -ne 0) { - Write-Host "Failed to build Chef $ChefVersion1 image" -ForegroundColor Red - exit 1 + Write-Host "WARNING: Docker prune failed, continuing anyway..." -ForegroundColor Yellow +} +else { + Write-Host "Docker environment cleaned successfully" -ForegroundColor Green } -Write-Host "`n=== Running Chef $ChefVersion1 container ===" -ForegroundColor Green +# Process each version to test +foreach ($versionInfo in $versionsToTest) { + $version = $versionInfo.Version + $msiPath = $versionInfo.MsiPath + + if ($msiPath -ne "") { + # MSI installation mode + Write-Host "`n=== Building Chef MSI container ===" -ForegroundColor Green + + # Copy MSI to temporary location + $tempMsi = Join-Path $PSScriptRoot "chef-installer.msi" + Copy-Item $msiPath $tempMsi -Force + + # Extract version from MSI filename or use "msi" as version + if ($msiPath -match 'chef-(\d+\.\d+\.\d+)') { + $extractedVersion = $matches[1] + $imageTag = "msi-$extractedVersion" + } + else { + $extractedVersion = "msi" + $imageTag = "msi" + } -# First, verify Chef installation and search for Chef.PowerShell.Wrapper.dll before running chef -Write-Host "Verifying Chef installation and searching for Chef.PowerShell.Wrapper.dll..." -ForegroundColor Yellow -docker run --rm ` - -v "${PWD}\shared:C:\shared" ` - chef-test:$ChefVersion1 ` - powershell -Command { - Write-Host '=== Chef Installation Verification ===' - $chefVersion = (chef-client --version) -replace 'Chef Infra Client: ', '' - Write-Host "Chef Version: $chefVersion" - $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' - $computerName = $env:COMPUTERNAME + $installDumpbinArg = if ($UseDumpbin) { "True" } else { "False" } + Write-Host "INSTALL_DUMPBIN: $installDumpbinArg" + docker build -f Dockerfile.msi --build-arg INSTALL_DUMPBIN=$installDumpbinArg -t chef-test:$imageTag . + Remove-Item $tempMsi -Force - Write-Host '' - Write-Host '=== Searching for Chef.PowerShell.Wrapper.dll ===' - $wrapperDlls = Get-ChildItem -Path 'C:\opscode\chef' -Include 'Chef.PowerShell.Wrapper.dll' -Recurse -ErrorAction SilentlyContinue + if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to build Chef MSI image" -ForegroundColor Red + Play-CompletionSound -Success $false + exit 1 + } - $wrapperInfo = if ($wrapperDlls) { - $wrapperDlls | ForEach-Object { - Write-Host "Found: $($_.FullName)" - Write-Host " Size: $($_.Length) bytes" - Write-Host " LastWriteTime: $($_.LastWriteTime)" - " Path: $($_.FullName)`n Size: $($_.Length) bytes`n LastWriteTime: $($_.LastWriteTime)" - } | Out-String - } else { - Write-Host 'Chef.PowerShell.Wrapper.dll not found' - ' Chef.PowerShell.Wrapper.dll not found' + # Run pre-check + Write-Host "`n=== Running Chef MSI container ===" -ForegroundColor Green + if ($FindDLLs) { + Write-Host "Verifying Chef installation and searching for DLLs..." -ForegroundColor Yellow + } + else { + Write-Host "Verifying Chef installation..." -ForegroundColor Yellow } - $chefClientPath = Get-Command chef-client | Select-Object -ExpandProperty Source - $output = "Chef Version: $chefVersion`nComputer Name: $computerName`nTimestamp: $timestamp`nChef Client Path: $chefClientPath`n`nChef.PowerShell.Wrapper.dll Search Results (Pre-Chef Run):`n$wrapperInfo" + $findDllsArg = if ($FindDLLs) { "True" } else { "False" } + docker run --rm ` + -v "${PWD}\shared:C:\shared" ` + -e FIND_DLLS=$findDllsArg ` + -e INSTALL_DUMPBIN=$installDumpbinArg ` + chef-test:$imageTag ` + powershell -Command { + Write-Host '=== Chef Installation Verification ===' + $chefVersion = (chef-client --version) -replace 'Chef Infra Client: ', '' + Write-Host "Chef Version: $chefVersion" + $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + $computerName = $env:COMPUTERNAME + + $findDlls = $env:FIND_DLLS -eq 'True' + $useDumpbin = $env:INSTALL_DUMPBIN -eq 'True' + $dllInfo = '' + + if ($findDlls) { + Write-Host '' + Write-Host '=== Searching for Chef.PowerShell.Wrapper.dll ===' + $wrapperDlls = Get-ChildItem -Path 'C:\opscode\chef' -Include 'Chef.PowerShell.Wrapper.dll' -Recurse -ErrorAction SilentlyContinue + + # Initialize dumpbin output variable + $script:dumpbinOutput = '' + + $wrapperInfo = if ($wrapperDlls) { + $wrapperDlls | ForEach-Object { + Write-Host "Found: $($_.FullName)" + Write-Host " Size: $($_.Length) bytes" + Write-Host " LastWriteTime: $($_.LastWriteTime)" + if ($useDumpbin) { + Write-Host '' + Write-Host 'Running dumpbin /dependents...' + try { + $script:dumpbinOutput = & dumpbin.exe /dependents $_.FullName 2>&1 | Out-String + Write-Host $script:dumpbinOutput + " Path: $($_.FullName)`n Size: $($_.Length) bytes`n LastWriteTime: $($_.LastWriteTime)`n`nDumpbin Output:`n$script:dumpbinOutput" + } + catch { + Write-Host " Error running dumpbin: $($_.Exception.Message)" -ForegroundColor Yellow + $script:dumpbinOutput = '' + " Path: $($_.FullName)`n Size: $($_.Length) bytes`n LastWriteTime: $($_.LastWriteTime)`n`nDumpbin Error: $($_.Exception.Message)" + } + } + else { + " Path: $($_.FullName)`n Size: $($_.Length) bytes`n LastWriteTime: $($_.LastWriteTime)" + } + Write-Host '' + } | Out-String + } + else { + Write-Host 'Chef.PowerShell.Wrapper.dll not found' + ' Chef.PowerShell.Wrapper.dll not found' + } + + Write-Host '' + Write-Host '=== Determining DLLs to search for ===' + + # Define the function inside the script block + function Get-DllsToFind { + param([string]$DumpbinOutput) + + $defaultDlls = @( + 'KERNEL32.dll', + 'VCRUNTIME140.dll', + 'api-ms-win-crt-runtime-l1-1-0.dll', + 'api-ms-win-crt-heap-l1-1-0.dll', + 'MSVCP140.dll', + 'mscoree.dll' + ) + + if ([string]::IsNullOrWhiteSpace($DumpbinOutput)) { + Write-Host ' Using default DLL list (dumpbin output not available)' + return $defaultDlls + } + + Write-Host ' Parsing dumpbin output for DLL dependencies...' + $dllPattern = '^\s+([a-zA-Z0-9\-\.]+\.dll)' + $extractedDlls = @() + + $DumpbinOutput -split "`n" | ForEach-Object { + if ($_ -match $dllPattern) { + $dllName = $matches[1].Trim() + if ($dllName -and $extractedDlls -notcontains $dllName) { + $extractedDlls += $dllName + Write-Host " Found dependency: $dllName" + } + } + } + + if ($extractedDlls.Count -gt 0) { + Write-Host " Extracted $($extractedDlls.Count) DLL(s) from dumpbin output" + return $extractedDlls + } + else { + Write-Host ' No DLLs found in dumpbin output, using default list' + return $defaultDlls + } + } + + $dllsToFind = Get-DllsToFind -DumpbinOutput $script:dumpbinOutput + + Write-Host '' + Write-Host '=== Searching for DLLs ===' + + $allDllInfo = '' + foreach ($dllName in $dllsToFind) { + Write-Host "Searching for $dllName..." + $foundDlls = Get-ChildItem -Path 'C:\' -Include $dllName -Recurse -ErrorAction SilentlyContinue + + if ($foundDlls) { + $allDllInfo += "$dllName found at:`n" + $foundDlls | ForEach-Object { + Write-Host " $($_.FullName)" + $allDllInfo += " $($_.FullName)`n" + } + } + else { + Write-Host " $dllName not found" + $allDllInfo += " $dllName not found`n" + } + } + + $dllInfo = "`n`nChef.PowerShell.Wrapper.dll Search Results (Pre-Chef Run):`n$wrapperInfo`nDLL Search Results:`n$allDllInfo" + } + + $chefClientPath = Get-Command chef-client | Select-Object -ExpandProperty Source + $output = "Chef Version: $chefVersion`nInstallation Method: MSI`nComputer Name: $computerName`nTimestamp: $timestamp`nChef Client Path: $chefClientPath$dllInfo" + + $outputFile = "C:\shared\chef-$chefVersion.txt" + Write-Host '' + Write-Host "Writing initial results to $outputFile" + $output | Out-File -FilePath $outputFile -Encoding UTF8 + Write-Host '' + } - $outputFile = "C:\shared\chef-$chefVersion.txt" - Write-Host '' - Write-Host "Writing initial results to $outputFile" - $output | Out-File -FilePath $outputFile -Encoding UTF8 - Write-Host '' - } - -# Now run the actual chef recipe -Write-Host "Running Chef recipe..." -ForegroundColor Yellow -docker run --rm ` - -e CHEF_LICENSE=accept-silent ` - -v "${PWD}\shared:C:\shared" ` - -v "${PWD}\cookbooks:C:\cookbooks" ` - chef-test:$ChefVersion1 ` - powershell -Command "chef-client -z -o recipe[test_recipe] --chef-license accept-silent" - -if ($LASTEXITCODE -ne 0) { - Write-Host "Chef $ChefVersion1 execution failed" -ForegroundColor Red - exit 1 -} - -# Build and run second Chef version -Write-Host "`n=== Building Chef $ChefVersion2 container ===" -ForegroundColor Green -docker build --build-arg CHEF_VERSION=$ChefVersion2 -t chef-test:$ChefVersion2 . - -if ($LASTEXITCODE -ne 0) { - Write-Host "Failed to build Chef $ChefVersion2 image" -ForegroundColor Red - exit 1 -} - -Write-Host "`n=== Running Chef $ChefVersion2 container ===" -ForegroundColor Green - -# First, verify Chef installation and search for Chef.PowerShell.Wrapper.dll before running chef -Write-Host "Verifying Chef installation and searching for Chef.PowerShell.Wrapper.dll..." -ForegroundColor Yellow -docker run --rm ` - -v "${PWD}\shared:C:\shared" ` - chef-test:$ChefVersion2 ` - powershell -Command { - Write-Host '=== Chef Installation Verification ===' - $chefVersion = (chef-client --version) -replace 'Chef Infra Client: ', '' - Write-Host "Chef Version: $chefVersion" - $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' - $computerName = $env:COMPUTERNAME + # Run chef recipe + Write-Host "Running Chef recipe..." -ForegroundColor Yellow + docker run --rm ` + -e CHEF_LICENSE=accept-silent ` + -v "${PWD}\shared:C:\shared" ` + -v "${PWD}\cookbooks:C:\cookbooks" ` + chef-test:$imageTag ` + powershell -Command { + $env:PATH = "$env:PATH;C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-18.1.0\bin\ruby_bin_folder\AMD64;C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-18.1.0\bin\ruby_bin_folder\AMD64\shared\Microsoft.NETCore.App\5.0.0" + #$env:PATH=$env:PATH;C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-18.1.0\bin\ruby_bin_folder\AMD64 + Write-Host "Path: $env:PATH" + chef-client -z -o recipe[test_recipe] --chef-license accept-silent + } - Write-Host '' - Write-Host '=== Searching for Chef.PowerShell.Wrapper.dll ===' - $wrapperDlls = Get-ChildItem -Path 'C:\opscode\chef' -Include 'Chef.PowerShell.Wrapper.dll' -Recurse -ErrorAction SilentlyContinue + if ($LASTEXITCODE -ne 0) { + Write-Host "Chef MSI execution failed (will continue to report results)" -ForegroundColor Yellow + $script:chefRunFailed = $true + } - $wrapperInfo = if ($wrapperDlls) { - $wrapperDlls | ForEach-Object { - Write-Host "Found: $($_.FullName)" - Write-Host " Size: $($_.Length) bytes" - Write-Host " LastWriteTime: $($_.LastWriteTime)" - " Path: $($_.FullName)`n Size: $($_.Length) bytes`n LastWriteTime: $($_.LastWriteTime)" - } | Out-String - } else { - Write-Host 'Chef.PowerShell.Wrapper.dll not found' - ' Chef.PowerShell.Wrapper.dll not found' + } + else { + # Omnitruck installation mode + Write-Host "`n=== Building Chef $version container ===" -ForegroundColor Green + $installDumpbinArg = if ($UseDumpbin) { "True" } else { "False" } + docker build --build-arg CHEF_VERSION=$version --build-arg INSTALL_DUMPBIN=$installDumpbinArg -t chef-test:$version . + + if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to build Chef $version image" -ForegroundColor Red + Play-CompletionSound -Success $false + exit 1 } - $chefClientPath = Get-Command chef-client | Select-Object -ExpandProperty Source - $output = "Chef Version: $chefVersion`nComputer Name: $computerName`nTimestamp: $timestamp`nChef Client Path: $chefClientPath`n`nChef.PowerShell.Wrapper.dll Search Results (Pre-Chef Run):`n$wrapperInfo" + Write-Host "`n=== Running Chef $version container ===" -ForegroundColor Green - $outputFile = "C:\shared\chef-$chefVersion.txt" - Write-Host '' - Write-Host "Writing initial results to $outputFile" - $output | Out-File -FilePath $outputFile -Encoding UTF8 - Write-Host '' - } - -# Now run the actual chef recipe -Write-Host "Running Chef recipe..." -ForegroundColor Yellow -docker run --rm ` - -e CHEF_LICENSE=accept-silent ` - -v "${PWD}\shared:C:\shared" ` - -v "${PWD}\cookbooks:C:\cookbooks" ` - chef-test:$ChefVersion2 ` - powershell -Command "chef-client -z -o recipe[test_recipe] --chef-license accept-silent" - -if ($LASTEXITCODE -ne 0) { - Write-Host "Chef $ChefVersion2 execution failed" -ForegroundColor Red - exit 1 + # First, verify Chef installation and search for DLLs before running chef + if ($FindDLLs) { + Write-Host "Verifying Chef installation and searching for DLLs..." -ForegroundColor Yellow + } + else { + Write-Host "Verifying Chef installation..." -ForegroundColor Yellow + } + + $findDllsArg = if ($FindDLLs) { "True" } else { "False" } + $installDumpbinArg = if ($UseDumpbin) { "True" } else { "False" } + docker run --rm ` + -v "${PWD}\shared:C:\shared" ` + -e FIND_DLLS=$findDllsArg ` + -e INSTALL_DUMPBIN=$installDumpbinArg ` + chef-test:$version ` + powershell -Command { + Write-Host '=== Chef Installation Verification ===' + $chefVersion = (chef-client --version) -replace 'Chef Infra Client: ', '' + Write-Host "Chef Version: $chefVersion" + $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + $computerName = $env:COMPUTERNAME + + $findDlls = $env:FIND_DLLS -eq 'True' + $useDumpbin = $env:INSTALL_DUMPBIN -eq 'True' + $dllInfo = '' + + if ($findDlls) { + Write-Host '' + Write-Host '=== Searching for Chef.PowerShell.Wrapper.dll ===' + $wrapperDlls = Get-ChildItem -Path 'C:\opscode\chef' -Include 'Chef.PowerShell.Wrapper.dll' -Recurse -ErrorAction SilentlyContinue + + # Initialize dumpbin output variable + $script:dumpbinOutput = '' + + $wrapperInfo = if ($wrapperDlls) { + $wrapperDlls | ForEach-Object { + Write-Host "Found: $($_.FullName)" + Write-Host " Size: $($_.Length) bytes" + Write-Host " LastWriteTime: $($_.LastWriteTime)" + if ($useDumpbin) { + Write-Host '' + Write-Host 'Running dumpbin /dependents...' + try { + $script:dumpbinOutput = & dumpbin.exe /dependents $_.FullName 2>&1 | Out-String + Write-Host $script:dumpbinOutput + " Path: $($_.FullName)`n Size: $($_.Length) bytes`n LastWriteTime: $($_.LastWriteTime)`n`nDumpbin Output:`n$script:dumpbinOutput" + } + catch { + Write-Host " Error running dumpbin: $($_.Exception.Message)" -ForegroundColor Yellow + $script:dumpbinOutput = '' + " Path: $($_.FullName)`n Size: $($_.Length) bytes`n LastWriteTime: $($_.LastWriteTime)`n`nDumpbin Error: $($_.Exception.Message)" + } + } + else { + " Path: $($_.FullName)`n Size: $($_.Length) bytes`n LastWriteTime: $($_.LastWriteTime)" + } + Write-Host '' + } | Out-String + } + else { + Write-Host 'Chef.PowerShell.Wrapper.dll not found' + ' Chef.PowerShell.Wrapper.dll not found' + } + + Write-Host '' + Write-Host '=== Determining DLLs to search for ===' + + # Define the function inside the script block + function Get-DllsToFind { + param([string]$DumpbinOutput) + + $defaultDlls = @( + 'KERNEL32.dll', + 'VCRUNTIME140.dll', + 'api-ms-win-crt-runtime-l1-1-0.dll', + 'api-ms-win-crt-heap-l1-1-0.dll', + 'MSVCP140.dll', + 'mscoree.dll' + ) + + if ([string]::IsNullOrWhiteSpace($DumpbinOutput)) { + Write-Host ' Using default DLL list (dumpbin output not available)' + return $defaultDlls + } + + Write-Host ' Parsing dumpbin output for DLL dependencies...' + $dllPattern = '^\s+([a-zA-Z0-9\-\.]+\.dll)' + $extractedDlls = @() + + $DumpbinOutput -split "`n" | ForEach-Object { + if ($_ -match $dllPattern) { + $dllName = $matches[1].Trim() + if ($dllName -and $extractedDlls -notcontains $dllName) { + $extractedDlls += $dllName + Write-Host " Found dependency: $dllName" + } + } + } + + if ($extractedDlls.Count -gt 0) { + Write-Host " Extracted $($extractedDlls.Count) DLL(s) from dumpbin output" + return $extractedDlls + } + else { + Write-Host ' No DLLs found in dumpbin output, using default list' + return $defaultDlls + } + } + + $dllsToFind = Get-DllsToFind -DumpbinOutput $script:dumpbinOutput + + Write-Host '' + Write-Host '=== Searching for DLLs ===' + + $allDllInfo = '' + foreach ($dllName in $dllsToFind) { + Write-Host "Searching for $dllName..." + $foundDlls = Get-ChildItem -Path 'C:\' -Include $dllName -Recurse -ErrorAction SilentlyContinue + + if ($foundDlls) { + $allDllInfo += "$dllName found at:`n" + $foundDlls | ForEach-Object { + Write-Host " $($_.FullName)" + $allDllInfo += " $($_.FullName)`n" + } + } + else { + Write-Host " $dllName not found" + $allDllInfo += " $dllName not found`n" + } + } + + $dllInfo = "`n`nChef.PowerShell.Wrapper.dll Search Results (Pre-Chef Run):`n$wrapperInfo`nDLL Search Results:`n$allDllInfo" + } + + $chefClientPath = Get-Command chef-client | Select-Object -ExpandProperty Source + $output = "Chef Version: $chefVersion`nInstallation Method: Omnitruck`nComputer Name: $computerName`nTimestamp: $timestamp`nChef Client Path: $chefClientPath$dllInfo" + + $outputFile = "C:\shared\chef-$chefVersion.txt" + Write-Host '' + Write-Host "Writing initial results to $outputFile" + $output | Out-File -FilePath $outputFile -Encoding UTF8 + Write-Host '' + } + + # Now run the actual chef recipe + Write-Host "Running Chef recipe..." -ForegroundColor Yellow + docker run --rm ` + -e CHEF_LICENSE=accept-silent ` + -v "${PWD}\shared:C:\shared" ` + -v "${PWD}\cookbooks:C:\cookbooks" ` + chef-test:$version ` + powershell -Command { + $env:PATH = $env:PATH; C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-18.1.0\bin\ruby_bin_folder\AMD64; C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-18.1.0\bin\ruby_bin_folder\AMD64\shared\Microsoft.NETCore.App\5.0.0 + #$env:PATH=$env:PATH;C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-18.1.0\bin\ruby_bin_folder\AMD64 + Write-Host "Path: $env:PATH" + chef-client -z -o recipe[test_recipe] --chef-license accept-silent + } + + + + if ($LASTEXITCODE -ne 0) { + Write-Host "Chef $version execution failed (will continue to report results)" -ForegroundColor Yellow + $script:chefRunFailed = $true + } + } } # Check results @@ -157,6 +522,7 @@ $results = Get-ChildItem $sharedDir -Filter "chef-*.txt" if ($results.Count -eq 0) { Write-Host "ERROR: No output files found in shared directory!" -ForegroundColor Red + Play-CompletionSound -Success $false exit 1 } @@ -171,7 +537,16 @@ foreach ($file in $results) { # Summary Write-Host "=== Test Summary ===" -ForegroundColor Cyan -Write-Host "Expected Chef versions: $ChefVersion1 and $ChefVersion2" -ForegroundColor White + +if ($SingleVersion) { + Write-Host "Expected Chef version: $ChefVersion1" -ForegroundColor White +} +elseif ($MsiFile -ne "") { + Write-Host "Tested Chef MSI: $MsiFile" -ForegroundColor White +} +else { + Write-Host "Expected Chef versions: $ChefVersion1 and $ChefVersion2" -ForegroundColor White +} $foundVersions = @() foreach ($file in $results) { @@ -182,10 +557,50 @@ foreach ($file in $results) { Write-Host "Found Chef versions: $($foundVersions -join ', ')" -ForegroundColor White -if ($foundVersions -contains $ChefVersion1 -and $foundVersions -contains $ChefVersion2) { - Write-Host "`nSUCCESS: Both Chef versions executed successfully!" -ForegroundColor Green -} else { - Write-Host "`nWARNING: Not all expected versions found" -ForegroundColor Yellow +# Determine success status +$success = $false + +if ($SingleVersion) { + if ($foundVersions -contains $ChefVersion1) { + Write-Host "`nSUCCESS: Chef version executed successfully!" -ForegroundColor Green + $success = $true + } + else { + Write-Host "`nWARNING: Expected version not found" -ForegroundColor Yellow + } +} +elseif ($MsiFile -ne "") { + if ($foundVersions.Count -gt 0) { + Write-Host "`nSUCCESS: Chef MSI executed successfully!" -ForegroundColor Green + $success = $true + } + else { + Write-Host "`nWARNING: No version output found" -ForegroundColor Yellow + } +} +else { + if ($foundVersions -contains $ChefVersion1 -and $foundVersions -contains $ChefVersion2) { + Write-Host "`nSUCCESS: Both Chef versions executed successfully!" -ForegroundColor Green + $success = $true + } + else { + Write-Host "`nWARNING: Not all expected versions found" -ForegroundColor Yellow + } } Write-Host "`n=== Test Complete ===" -ForegroundColor Cyan + +# Check if any chef runs failed and report +if ($script:chefRunFailed) { + Write-Host "`nWARNING: One or more Chef runs encountered errors!" -ForegroundColor Red + Write-Host "However, DLL search and reporting completed successfully." -ForegroundColor Yellow + $success = $false +} + +# Play completion sound +Play-CompletionSound -Success $success + +# Exit with error code if chef run failed +if ($script:chefRunFailed) { + exit 1 +} diff --git a/test-dll-remediation.ps1 b/test-dll-remediation.ps1 new file mode 100644 index 0000000..9e238fe --- /dev/null +++ b/test-dll-remediation.ps1 @@ -0,0 +1,429 @@ +# Script to test Chef PowerShell DLL error and gem pristine remediation +param( + [string]$MsiFile = "omnibus-ruby_chef_pkg_chef-client-18.8.50-1-x64.msi" +) + +$ErrorActionPreference = "Stop" + +Write-Host "=== Chef PowerShell DLL Error Detection and Gem Pristine Remediation Test ===" -ForegroundColor Cyan +Write-Host "MSI File: $MsiFile" -ForegroundColor Yellow +Write-Host "" + +# Verify MSI file exists +if (-not (Test-Path $MsiFile)) { + Write-Host "ERROR: MSI file not found: $MsiFile" -ForegroundColor Red + exit 1 +} + +# Create shared directory if it doesn't exist +$sharedDir = Join-Path $PSScriptRoot "shared" +if (-not (Test-Path $sharedDir)) { + Write-Host "Creating shared directory..." -ForegroundColor Yellow + New-Item -ItemType Directory -Path $sharedDir | Out-Null +} + +# Clean up any previous test files +Write-Host "Cleaning previous test files..." -ForegroundColor Yellow +Get-ChildItem $sharedDir -Filter "*dll-remediation*" | Remove-Item -Force -ErrorAction SilentlyContinue + +# Copy MSI to temporary location for Docker build +Write-Host "Preparing MSI for Docker build..." -ForegroundColor Yellow +$tempMsi = Join-Path $PSScriptRoot "chef-installer.msi" +Copy-Item $MsiFile $tempMsi -Force + +try { + # Clean up Docker environment before building + Write-Host "`n=== Cleaning Docker Environment ===" -ForegroundColor Green + Write-Host "Pruning Docker system (removing unused containers, networks, images)..." -ForegroundColor Yellow + docker system prune -f + + if ($LASTEXITCODE -ne 0) { + Write-Host "WARNING: Docker prune failed, continuing anyway..." -ForegroundColor Yellow + } + else { + Write-Host "Docker environment cleaned successfully" -ForegroundColor Green + } + + # Build Docker image with MSI install + Write-Host "`n=== Building Chef MSI Docker Image ===" -ForegroundColor Green + docker build -f Dockerfile.msi --build-arg INSTALL_DUMPBIN=False -t chef-dll-test:latest . + + if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to build Chef MSI image" -ForegroundColor Red + exit 1 + } + + # Create comprehensive test script + $testScript = @' +Write-Host "=======================================================================" +Write-Host "Chef PowerShell DLL Error Detection and Remediation Test" +Write-Host "=======================================================================" + +$testResults = @{ + Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + PrePristineTest = @{ + Success = $false + Error = "" + Output = "" + DllExists = $false + DllPath = "" + } + GemPristineOperation = @{ + Success = $false + Error = "" + Output = "" + } + PostPristineTest = @{ + Success = $false + Error = "" + Output = "" + DllExists = $false + DllPath = "" + } + FileSystemChanges = @{ + FilesChanged = @() + FilesAdded = @() + FilesRemoved = @() + } +} + +Write-Host "" +Write-Host "=== Phase 1: Pre-Pristine DLL and Chef Test ===" + +# Check if DLL exists before testing +$dllPath = "C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-18.6.3\bin\ruby_bin_folder\AMD64\Chef.PowerShell.Wrapper.dll" +if (Test-Path $dllPath) { + Write-Host "✓ Chef.PowerShell.Wrapper.dll found at: $dllPath" + $testResults.PrePristineTest.DllExists = $true + $testResults.PrePristineTest.DllPath = $dllPath + + # Check DLL properties + $dllInfo = Get-Item $dllPath + Write-Host " Size: $($dllInfo.Length) bytes" + Write-Host " LastWriteTime: $($dllInfo.LastWriteTime)" + + # Try to get file version info if available + try { + $versionInfo = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($dllPath) + Write-Host " File Version: $($versionInfo.FileVersion)" + Write-Host " Product Version: $($versionInfo.ProductVersion)" + } catch { + Write-Host " Version info not available: $($_.Exception.Message)" + } +} else { + Write-Host "✗ Chef.PowerShell.Wrapper.dll NOT found at: $dllPath" + $testResults.PrePristineTest.DllExists = $false +} + +Write-Host "" +Write-Host "Testing Chef PowerShell functionality (Pre-Pristine)..." + +# Capture initial gem directory state +$initialFiles = @{} +$gemDir = "C:\opscode\chef\embedded\lib\ruby\gems\3.1.0\gems\chef-powershell-18.6.3" +if (Test-Path $gemDir) { + Get-ChildItem -Path $gemDir -Recurse -Force | ForEach-Object { + $relativePath = $_.FullName.Replace($gemDir, "") + $initialFiles[$relativePath] = @{ + Size = if (-not $_.PSIsContainer) { $_.Length } else { 0 } + LastWriteTime = $_.LastWriteTime + IsDirectory = $_.PSIsContainer + } + } +} + +# Test Chef with PowerShell recipe +try { + Write-Host "Executing: chef-client -z -o recipe[test_recipe] --chef-license accept-silent" + $chefOutput = & chef-client -z -o recipe[test_recipe] --chef-license accept-silent 2>&1 | Out-String + + if ($LASTEXITCODE -eq 0) { + Write-Host "✓ Chef run completed successfully (Pre-Pristine)" -ForegroundColor Green + $testResults.PrePristineTest.Success = $true + $testResults.PrePristineTest.Output = $chefOutput + } else { + Write-Host "✗ Chef run failed with exit code: $LASTEXITCODE" -ForegroundColor Red + $testResults.PrePristineTest.Success = $false + $testResults.PrePristineTest.Error = "Exit code: $LASTEXITCODE" + $testResults.PrePristineTest.Output = $chefOutput + + # Check for specific DLL error + if ($chefOutput -match "Could not open library.*Chef\.PowerShell\.Wrapper\.dll.*Failed with error 126") { + Write-Host "✗ DETECTED: Chef.PowerShell.Wrapper.dll loading error (Error 126 - Module not found)" -ForegroundColor Red + $testResults.PrePristineTest.Error = "DLL Error 126: Chef.PowerShell.Wrapper.dll could not be loaded" + } + } +} catch { + Write-Host "✗ Chef execution failed with exception: $($_.Exception.Message)" -ForegroundColor Red + $testResults.PrePristineTest.Success = $false + $testResults.PrePristineTest.Error = $_.Exception.Message +} + +Write-Host "" +Write-Host "=== Phase 2: Gem Pristine Operation ===" + +# Run gem pristine chef-powershell +try { + Write-Host "Executing: gem pristine chef-powershell" + $pristineOutput = & "C:\opscode\chef\embedded\bin\gem" pristine chef-powershell 2>&1 | Out-String + + if ($LASTEXITCODE -eq 0) { + Write-Host "✓ Gem pristine completed successfully" -ForegroundColor Green + $testResults.GemPristineOperation.Success = $true + $testResults.GemPristineOperation.Output = $pristineOutput + } else { + Write-Host "✗ Gem pristine failed with exit code: $LASTEXITCODE" -ForegroundColor Red + $testResults.GemPristineOperation.Success = $false + $testResults.GemPristineOperation.Error = "Exit code: $LASTEXITCODE" + $testResults.GemPristineOperation.Output = $pristineOutput + } + + Write-Host "Gem pristine output:" + Write-Host $pristineOutput +} catch { + Write-Host "✗ Gem pristine failed with exception: $($_.Exception.Message)" -ForegroundColor Red + $testResults.GemPristineOperation.Success = $false + $testResults.GemPristineOperation.Error = $_.Exception.Message +} + +Write-Host "" +Write-Host "=== Phase 3: Post-Pristine DLL and Chef Test ===" + +# Check if DLL exists after pristine +if (Test-Path $dllPath) { + Write-Host "✓ Chef.PowerShell.Wrapper.dll found at: $dllPath (Post-Pristine)" + $testResults.PostPristineTest.DllExists = $true + $testResults.PostPristineTest.DllPath = $dllPath + + # Check if DLL was modified + $dllInfoPost = Get-Item $dllPath + Write-Host " Size: $($dllInfoPost.Length) bytes" + Write-Host " LastWriteTime: $($dllInfoPost.LastWriteTime)" + + if ($testResults.PrePristineTest.DllExists) { + $preTime = [DateTime]::Parse("$($dllInfo.LastWriteTime)") + $postTime = [DateTime]::Parse("$($dllInfoPost.LastWriteTime)") + if ($postTime -gt $preTime) { + Write-Host " ✓ DLL was updated by gem pristine" -ForegroundColor Green + } else { + Write-Host " ◦ DLL timestamp unchanged" -ForegroundColor Yellow + } + } +} else { + Write-Host "✗ Chef.PowerShell.Wrapper.dll NOT found at: $dllPath (Post-Pristine)" + $testResults.PostPristineTest.DllExists = $false +} + +# Capture file system changes +if (Test-Path $gemDir) { + Get-ChildItem -Path $gemDir -Recurse -Force | ForEach-Object { + $relativePath = $_.FullName.Replace($gemDir, "") + $currentFile = @{ + Size = if (-not $_.PSIsContainer) { $_.Length } else { 0 } + LastWriteTime = $_.LastWriteTime + IsDirectory = $_.PSIsContainer + } + + if ($initialFiles.ContainsKey($relativePath)) { + $initialFile = $initialFiles[$relativePath] + if ($initialFile.LastWriteTime -ne $currentFile.LastWriteTime -or + $initialFile.Size -ne $currentFile.Size) { + $testResults.FileSystemChanges.FilesChanged += $relativePath + } + } else { + $testResults.FileSystemChanges.FilesAdded += $relativePath + } + } +} + +# Check for removed files +foreach ($initialPath in $initialFiles.Keys) { + $fullPath = Join-Path $gemDir $initialPath + if (-not (Test-Path $fullPath)) { + $testResults.FileSystemChanges.FilesRemoved += $initialPath + } +} + +Write-Host "" +Write-Host "Testing Chef PowerShell functionality (Post-Pristine)..." + +# Test Chef with PowerShell recipe again +try { + Write-Host "Executing: chef-client -z -o recipe[test_recipe] --chef-license accept-silent" + $chefOutputPost = & chef-client -z -o recipe[test_recipe] --chef-license accept-silent 2>&1 | Out-String + + if ($LASTEXITCODE -eq 0) { + Write-Host "✓ Chef run completed successfully (Post-Pristine)" -ForegroundColor Green + $testResults.PostPristineTest.Success = $true + $testResults.PostPristineTest.Output = $chefOutputPost + } else { + Write-Host "✗ Chef run failed with exit code: $LASTEXITCODE (Post-Pristine)" -ForegroundColor Red + $testResults.PostPristineTest.Success = $false + $testResults.PostPristineTest.Error = "Exit code: $LASTEXITCODE" + $testResults.PostPristineTest.Output = $chefOutputPost + + # Check for specific DLL error again + if ($chefOutputPost -match "Could not open library.*Chef\.PowerShell\.Wrapper\.dll.*Failed with error 126") { + Write-Host "✗ DLL loading error persists after gem pristine" -ForegroundColor Red + $testResults.PostPristineTest.Error = "DLL Error 126: Chef.PowerShell.Wrapper.dll could not be loaded (persists after pristine)" + } + } +} catch { + Write-Host "✗ Chef execution failed with exception: $($_.Exception.Message)" -ForegroundColor Red + $testResults.PostPristineTest.Success = $false + $testResults.PostPristineTest.Error = $_.Exception.Message +} + +Write-Host "" +Write-Host "=== Test Results Summary ===" +Write-Host "Pre-Pristine DLL Present: $($testResults.PrePristineTest.DllExists)" +Write-Host "Pre-Pristine Chef Success: $($testResults.PrePristineTest.Success)" +Write-Host "Gem Pristine Success: $($testResults.GemPristineOperation.Success)" +Write-Host "Post-Pristine DLL Present: $($testResults.PostPristineTest.DllExists)" +Write-Host "Post-Pristine Chef Success: $($testResults.PostPristineTest.Success)" +Write-Host "Files Changed: $($testResults.FileSystemChanges.FilesChanged.Count)" +Write-Host "Files Added: $($testResults.FileSystemChanges.FilesAdded.Count)" +Write-Host "Files Removed: $($testResults.FileSystemChanges.FilesRemoved.Count)" + +# Save detailed results to JSON +$outputPath = "C:\shared\dll-remediation-test-results.json" +$testResults | ConvertTo-Json -Depth 10 | Out-File -FilePath $outputPath -Encoding UTF8 +Write-Host "" +Write-Host "Detailed results saved to: $outputPath" + +Write-Host "" +Write-Host "=======================================================================" +'@ + + # Run the comprehensive test + Write-Host "`n=== Running DLL Error Detection and Remediation Test ===" -ForegroundColor Green + docker run --rm -e CHEF_LICENSE=accept-silent -v "${PWD}\shared:C:\shared" -v "${PWD}\cookbooks:C:\cookbooks" chef-dll-test:latest powershell -Command $testScript + + # Generate summary report + Write-Host "`n=== Generating Summary Report ===" -ForegroundColor Green + + $resultsFile = Join-Path $sharedDir "dll-remediation-test-results.json" + + if (Test-Path $resultsFile) { + $results = Get-Content $resultsFile | ConvertFrom-Json + + # Create summary report + $reportPath = Join-Path $sharedDir "dll-remediation-summary.md" + + $report = @" +# Chef PowerShell DLL Error Detection and Gem Pristine Remediation Report + +**Generated:** $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') +**Test Timestamp:** $($results.Timestamp) + +## Executive Summary + +This test was designed to detect Chef PowerShell DLL loading errors and evaluate whether `gem pristine chef-powershell` can remediate packaging issues. + +## Test Results Overview + +| Phase | DLL Present | Chef Success | Error Details | +|-------|-------------|--------------|---------------| +| **Pre-Pristine** | $($results.PrePristineTest.DllExists) | $($results.PrePristineTest.Success) | $($results.PrePristineTest.Error) | +| **Gem Pristine** | - | $($results.GemPristineOperation.Success) | $($results.GemPristineOperation.Error) | +| **Post-Pristine** | $($results.PostPristineTest.DllExists) | $($results.PostPristineTest.Success) | $($results.PostPristineTest.Error) | + +## Detailed Analysis + +### Pre-Pristine State +- **DLL Location:** $($results.PrePristineTest.DllPath) +- **DLL Exists:** $($results.PrePristineTest.DllExists) +- **Chef Functionality:** $(if ($results.PrePristineTest.Success) { "✅ Working" } else { "❌ Failed" }) +- **Error Type:** $($results.PrePristineTest.Error) + +### Gem Pristine Operation +- **Success:** $(if ($results.GemPristineOperation.Success) { "✅ Completed" } else { "❌ Failed" }) +- **Error:** $($results.GemPristineOperation.Error) + +### Post-Pristine State +- **DLL Exists:** $($results.PostPristineTest.DllExists) +- **Chef Functionality:** $(if ($results.PostPristineTest.Success) { "✅ Working" } else { "❌ Failed" }) +- **Error Type:** $($results.PostPristineTest.Error) + +### File System Changes +- **Files Modified:** $($results.FileSystemChanges.FilesChanged.Count) +- **Files Added:** $($results.FileSystemChanges.FilesAdded.Count) +- **Files Removed:** $($results.FileSystemChanges.FilesRemoved.Count) + +"@ + + if ($results.FileSystemChanges.FilesChanged.Count -gt 0) { + $report += "`n#### Files Modified by Gem Pristine`n" + foreach ($file in $results.FileSystemChanges.FilesChanged) { + $report += "- $file`n" + } + } + + if ($results.FileSystemChanges.FilesAdded.Count -gt 0) { + $report += "`n#### Files Added by Gem Pristine`n" + foreach ($file in $results.FileSystemChanges.FilesAdded) { + $report += "- $file`n" + } + } + + if ($results.FileSystemChanges.FilesRemoved.Count -gt 0) { + $report += "`n#### Files Removed by Gem Pristine`n" + foreach ($file in $results.FileSystemChanges.FilesRemoved) { + $report += "- $file`n" + } + } + + # Add conclusions + $report += "`n## Conclusions`n" + + if (-not $results.PrePristineTest.Success -and $results.PostPristineTest.Success) { + $report += "✅ **SUCCESS** - Gem pristine resolved the Chef PowerShell DLL error!`n" + } + elseif (-not $results.PrePristineTest.Success -and -not $results.PostPristineTest.Success) { + $report += "❌ **UNRESOLVED** - Gem pristine did not resolve the Chef PowerShell DLL error`n" + } + elseif ($results.PrePristineTest.Success -and $results.PostPristineTest.Success) { + $report += "✅ **STABLE** - No DLL errors detected before or after gem pristine`n" + } + else { + $report += "❌ **REGRESSION** - Gem pristine introduced new issues`n" + } + + if ($results.FileSystemChanges.FilesChanged.Count -gt 0 -or + $results.FileSystemChanges.FilesAdded.Count -gt 0 -or + $results.FileSystemChanges.FilesRemoved.Count -gt 0) { + $report += "`nGem pristine made changes to $($results.FileSystemChanges.FilesChanged.Count + $results.FileSystemChanges.FilesAdded.Count + $results.FileSystemChanges.FilesRemoved.Count) files, indicating it attempted remediation.`n" + } + else { + $report += "`nGem pristine made no file system changes, suggesting the gem was already in pristine condition.`n" + } + + # Save report + $report | Out-File -FilePath $reportPath -Encoding UTF8 + + Write-Host "Summary report generated: $reportPath" -ForegroundColor Green + Write-Host "" + Write-Host "=== Key Findings ===" -ForegroundColor Cyan + Write-Host "Pre-pristine Chef success: $(if ($results.PrePristineTest.Success) { 'YES' } else { 'NO' })" -ForegroundColor $(if ($results.PrePristineTest.Success) { 'Green' } else { 'Red' }) + Write-Host "Post-pristine Chef success: $(if ($results.PostPristineTest.Success) { 'YES' } else { 'NO' })" -ForegroundColor $(if ($results.PostPristineTest.Success) { 'Green' } else { 'Red' }) + Write-Host "Gem pristine success: $(if ($results.GemPristineOperation.Success) { 'YES' } else { 'NO' })" -ForegroundColor $(if ($results.GemPristineOperation.Success) { 'Green' } else { 'Red' }) + Write-Host "Files changed by pristine: $($results.FileSystemChanges.FilesChanged.Count + $results.FileSystemChanges.FilesAdded.Count + $results.FileSystemChanges.FilesRemoved.Count)" -ForegroundColor Yellow + + } + else { + Write-Host "ERROR: Could not find test results file" -ForegroundColor Red + exit 1 + } + +} +finally { + # Clean up temporary MSI file + if (Test-Path $tempMsi) { + Remove-Item $tempMsi -Force -ErrorAction SilentlyContinue + } +} + +Write-Host "`n=== DLL Remediation Test Complete ===" -ForegroundColor Cyan +Write-Host "Check the shared/ directory for detailed results:" -ForegroundColor Green +Write-Host "- dll-remediation-test-results.json - Detailed test data" -ForegroundColor White +Write-Host "- dll-remediation-summary.md - Human-readable analysis" -ForegroundColor White \ No newline at end of file