dist/Export-WindowsUpdateTools.ps1

<#
.SYNOPSIS
    Package the WindowsUpdateTools module into a distributable ZIP.
 
.DESCRIPTION
    Reads the module manifest to determine version and creates a ZIP archive of the module
    directory suitable for copying to another computer. The ZIP contains the full module
    folder structure (Public/Private files and the .psd1 manifest).
 
.PARAMETER ModuleRoot
    Path to the WindowsUpdateTools module folder. Defaults to the parent directory of this script.
 
.PARAMETER OutputDirectory
    Where to place the generated ZIP. Defaults to the current 'dist' directory.
 
.PARAMETER Force
    Overwrite existing archive if present.
 
.EXAMPLE
    .\Export-WindowsUpdateTools.ps1
 
.EXAMPLE
    .\Export-WindowsUpdateTools.ps1 -Force
#>

[CmdletBinding()]
param(
    [string]$ModuleRoot = (Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)),
    [string]$OutputDirectory = (Split-Path -Parent $MyInvocation.MyCommand.Path),
    [switch]$Force
)

if (-not (Test-Path $ModuleRoot)) { throw "Module root not found: $ModuleRoot" }

Write-Host "Packaging WindowsUpdateTools module..." -ForegroundColor Cyan
Write-Host "Module source: $ModuleRoot" -ForegroundColor Gray

$psd1 = Join-Path $ModuleRoot 'WindowsUpdateTools.psd1'
if (-not (Test-Path $psd1)) { throw "Module manifest not found at: $psd1" }

# Read version from manifest
try {
    $manifestData = Import-PowerShellDataFile -Path $psd1 -ErrorAction Stop
    $version = $manifestData.ModuleVersion
} catch {
    # Fallback: parse manually if Import-PowerShellDataFile fails
    $content = Get-Content -Path $psd1 -Raw
    if ($content -match "ModuleVersion\s*=\s*'([^']+)'") { 
        $version = $matches[1] 
    } else { 
        $version = '0.0.0' 
        Write-Warning "Could not determine module version from manifest"
    }
}

if (-not $OutputDirectory) { $OutputDirectory = Split-Path -Parent $MyInvocation.MyCommand.Path }
if (-not (Test-Path $OutputDirectory)) { New-Item -Path $OutputDirectory -ItemType Directory -Force | Out-Null }

$moduleName = (Split-Path $ModuleRoot -Leaf)
$zipPath = Join-Path $OutputDirectory "${moduleName}-$version.zip"

Write-Host "Version: $version" -ForegroundColor Gray
Write-Host "Output: $zipPath" -ForegroundColor Gray

if ((Test-Path $zipPath) -and -not $Force) {
    Write-Host "Archive already exists: $zipPath" -ForegroundColor Yellow
    return $zipPath
}

# Create temporary staging folder to ensure ZIP contains top-level module folder
$temp = Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString())
New-Item -Path $temp -ItemType Directory -Force | Out-Null
$staging = Join-Path $temp $moduleName
# Ensure staging subfolder exists before copying; Copy-Item will fail if destination is a file
New-Item -Path $staging -ItemType Directory -Force | Out-Null

# Use -Container to preserve the folder structure and guard against copying into an existing file
# Exclude the 'dist' folder to avoid including old exports
try {
    Get-ChildItem -Path $ModuleRoot -Force | Where-Object { $_.Name -ne 'dist' } | ForEach-Object {
        Copy-Item -Path $_.FullName -Destination $staging -Recurse -Force -ErrorAction Stop
    }
} catch {
    # If copy fails, attempt a more granular copy to avoid container/leaf conflicts
    Get-ChildItem -Path $ModuleRoot -Force | Where-Object { $_.Name -ne 'dist' } | ForEach-Object {
        $src = $_.FullName
        $dest = Join-Path $staging $_.Name
        if ($_.PSIsContainer) { Copy-Item -Path $src -Destination $dest -Recurse -Force -ErrorAction SilentlyContinue } else { Copy-Item -Path $src -Destination $dest -Force -ErrorAction SilentlyContinue }
    }
}

try {
    if (Test-Path $zipPath) { Remove-Item -Path $zipPath -Force }
    Compress-Archive -Path $staging -DestinationPath $zipPath -Force
    Write-Host "Created archive: $zipPath" -ForegroundColor Green
    return $zipPath
} finally {
    Remove-Item -Path $temp -Recurse -Force -ErrorAction SilentlyContinue
}