- Create file, for example edit script.shin powershell, then paste the following script.
- Then run with ./script.sh.
# Initialize log file with timestamp
$Timestamp = Get-Date -Format "yyyyMMdd_HHmm"
$LogFile = "pdf_combine_$Timestamp.log"
# Logging function to capture messages with timestamp and type
function Write-Log {
    param (
        [string]$Type,
        [string]$Message
    )
    $LogTimestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $LogMessage = "[$LogTimestamp] [$Type] $Message"
    Write-Output $LogMessage | Out-File -FilePath $LogFile -Append
    Write-Output $LogMessage
}
# Log script start
Write-Log -Type "INFO" -Message "Starting PDF combine script"
# Check if required tools are installed
if (-not (Get-Command pdftk -ErrorAction SilentlyContinue)) {
    Write-Log -Type "ERROR" -Message "pdftk is required but not installed. Install it via Scoop: scoop install pdftk"
    exit 1
}
if (-not (Get-Command qpdf -ErrorAction SilentlyContinue)) {
    Write-Log -Type "ERROR" -Message "qpdf is required but not installed. Install it via Scoop: scoop install qpdf"
    exit 1
}
# Check for wkhtmltopdf (preferred method)
$UseWkHtml = $false
if (Get-Command wkhtmltopdf -ErrorAction SilentlyContinue) {
    $UseWkHtml = $true
    Write-Log -Type "INFO" -Message "wkhtmltopdf found - using for footer generation"
} else {
    Write-Log -Type "WARNING" -Message "wkhtmltopdf not found. Install it via: scoop install wkhtmltopdf"
    if (-not (Get-Command gs -ErrorAction SilentlyContinue)) {
        Write-Log -Type "ERROR" -Message "Ghostscript is required as fallback but not installed. Install it via Scoop: scoop install ghostscript"
        exit 1
    }
    Write-Log -Type "INFO" -Message "Using Ghostscript as fallback for footer generation"
}
# Create a temporary directory for intermediate files
$TempDir = New-Item -ItemType Directory -Path (Join-Path $env:TEMP "pdf_combine_$Timestamp") -Force
Write-Log -Type "INFO" -Message "Created temporary directory: $TempDir"
$OutputPDF = "combined_output.pdf"
$FooterPDFs = @()
# Get current directory for relative path calculation
$CurrentDir = (Get-Location).Path
# Find all PDF files in the current directory and subdirectories, excluding the output PDF
$PDFs = Get-ChildItem -Path . -Filter "*.pdf" -Recurse -File | Where-Object { $_.Name -ne $OutputPDF }
if ($PDFs.Count -eq 0) {
    Write-Log -Type "ERROR" -Message "No PDF files found in the current directory or subdirectories (excluding $OutputPDF)."
    Remove-Item -Path $TempDir -Recurse -Force
    Write-Log -Type "INFO" -Message "Cleaned up temporary directory: $TempDir"
    exit 1
}
Write-Log -Type "INFO" -Message "Found $($PDFs.Count) PDF files to process"
# Counter to ensure unique temporary filenames
$Counter = 0
foreach ($PDF in $PDFs) {
    $Counter++
    $Basename = [System.IO.Path]::GetFileNameWithoutExtension($PDF.FullName)
    
    # Get relative path from current directory
    $RelativePath = [System.IO.Path]::GetRelativePath($CurrentDir, $PDF.DirectoryName)
    if ($RelativePath -eq ".") {
        $FooterText = $Basename
    } else {
        $FooterText = "$RelativePath\$Basename"
    }
    
    $UniqueSuffix = "$Counter"
    $TempPDF = Join-Path $TempDir "$Basename`_footered_$UniqueSuffix.pdf"
    $FooterPDF = Join-Path $TempDir "$Basename`_footer_$UniqueSuffix.pdf"
    Write-Log -Type "INFO" -Message "Processing PDF [$Counter/$($PDFs.Count)]: $($PDF.Name)"
    Write-Log -Type "INFO" -Message "Footer text: $FooterText"
    # Create footer PDF
    $FooterCreated = $false
    
    if ($UseWkHtml) {
        # Create HTML file for footer
        $TempHTML = Join-Path $TempDir "$Basename`_footer_$UniqueSuffix.html"
        $HTMLContent = @"
<!DOCTYPE html>
<html>
<head>
    <style>
        body { 
            margin: 0; 
            padding: 0; 
            height: 100vh; 
            display: flex; 
            justify-content: center; 
            align-items: flex-end; 
            font-family: Arial, sans-serif; 
            font-size: 10px;
        }
        .footer { 
            text-align: center; 
            padding-bottom: 10px; 
            color: black;
        }
    </style>
</head>
<body>
    <div class="footer">$FooterText</div>
</body>
</html>
"@
        
        $HTMLContent | Out-File -FilePath $TempHTML -Encoding UTF8
        
        # Convert HTML to PDF using wkhtmltopdf
        $WkArgs = @(
            "--page-size", "Letter",
            "--margin-top", "0",
            "--margin-right", "0", 
            "--margin-bottom", "0",
            "--margin-left", "0",
            "--disable-smart-shrinking",
            $TempHTML,
            $FooterPDF
        )
        
        Write-Log -Type "INFO" -Message "Creating footer PDF with wkhtmltopdf"
        $WkOutput = & wkhtmltopdf $WkArgs 2>&1
        $WkExitCode = $LASTEXITCODE
        
        if ($WkExitCode -eq 0) {
            $FooterCreated = $true
            Write-Log -Type "INFO" -Message "Successfully created footer PDF using wkhtmltopdf"
        } else {
            Write-Log -Type "ERROR" -Message "wkhtmltopdf failed with exit code: $WkExitCode"
            Write-Log -Type "ERROR" -Message "wkhtmltopdf output: $($WkOutput -join "`n")"
        }
        
        # Clean up HTML file
        if (Test-Path $TempHTML) {
            Remove-Item $TempHTML -Force
        }
    }
    
    # Fallback to Ghostscript if wkhtmltopdf failed or not available
    if (-not $FooterCreated) {
        Write-Log -Type "INFO" -Message "Using Ghostscript fallback for footer creation"
        $GsArgs = @(
            "-sDEVICE=pdfwrite",
            "-o", $FooterPDF,
            "-c", 
            "<</PageSize [612 792]>> setpagedevice",
            "/Courier findfont 10 scalefont setfont",
            "306 50 moveto",
            "($FooterText) dup stringwidth pop 2 div neg 0 rmoveto show",
            "showpage"
        )
        
        $GsOutput = & gs $GsArgs 2>&1
        $GsExitCode = $LASTEXITCODE
        
        if ($GsExitCode -eq 0) {
            $FooterCreated = $true
            Write-Log -Type "INFO" -Message "Successfully created footer PDF using Ghostscript"
        } else {
            Write-Log -Type "ERROR" -Message "Ghostscript failed with exit code: $GsExitCode"
            Write-Log -Type "ERROR" -Message "Ghostscript output: $($GsOutput -join "`n")"
        }
    }
    # Verify the footer PDF was created
    if (-not $FooterCreated -or -not (Test-Path $FooterPDF)) {
        Write-Log -Type "ERROR" -Message "Failed to create footer PDF for $($PDF.FullName). Skipping."
        continue
    }
    # Get page count of original PDF
    $PageCountOutput = & qpdf --show-npages $PDF.FullName 2>&1
    $PageCountExitCode = $LASTEXITCODE
    
    if ($PageCountExitCode -ne 0) {
        Write-Log -Type "ERROR" -Message "Failed to determine page count for $($PDF.FullName). Skipping."
        continue
    }
    
    $PageCount = $PageCountOutput -replace '[^\d]', ''
    if (-not $PageCount -or $PageCount -eq 0) {
        Write-Log -Type "ERROR" -Message "Invalid page count ($PageCount) for $($PDF.FullName). Skipping."
        continue
    }
    Write-Log -Type "INFO" -Message "Page count: $PageCount"
    # Overlay footer on original PDF
    $QpdfArgs = @(
        $PDF.FullName,
        "--overlay", $FooterPDF,
        "--repeat=1",
        "--to=1-$PageCount",
        "--",
        $TempPDF
    )
    
    Write-Log -Type "INFO" -Message "Overlaying footer on PDF"
    $QpdfOutput = & qpdf $QpdfArgs 2>&1
    $QpdfExitCode = $LASTEXITCODE
    
    if ($QpdfExitCode -ne 0) {
        Write-Log -Type "ERROR" -Message "Failed to add footer to $($PDF.FullName). Exit code: $QpdfExitCode"
        Write-Log -Type "ERROR" -Message "qpdf output: $($QpdfOutput -join "`n")"
        continue
    }
    # Verify the footered PDF was created
    if (-not (Test-Path $TempPDF)) {
        Write-Log -Type "ERROR" -Message "Footered PDF not found at $TempPDF for $($PDF.FullName)"
        continue
    }
    # Add to list for merging
    $FooterPDFs += $TempPDF
    Write-Log -Type "INFO" -Message "Successfully processed PDF: $($PDF.Name)"
}
# Check if any PDFs were successfully processed
if ($FooterPDFs.Count -eq 0) {
    Write-Log -Type "ERROR" -Message "No PDFs were successfully processed for merging."
    Remove-Item -Path $TempDir -Recurse -Force
    Write-Log -Type "INFO" -Message "Cleaned up temporary directory: $TempDir"
    exit 1
}
Write-Log -Type "INFO" -Message "Successfully processed $($FooterPDFs.Count) PDFs. Starting merge process."
# Merge all footered PDFs into one
$PdtkArgs = @($FooterPDFs + @("cat", "output", $OutputPDF))
Write-Log -Type "INFO" -Message "Merging PDFs with pdftk"
$PdtkOutput = & pdftk $PdtkArgs 2>&1
$PdtkExitCode = $LASTEXITCODE
if ($PdtkExitCode -ne 0) {
    Write-Log -Type "ERROR" -Message "Failed to merge PDFs into $OutputPDF. Exit code: $PdtkExitCode"
    Write-Log -Type "ERROR" -Message "pdftk output: $($PdtkOutput -join "`n")"
    Write-Log -Type "INFO" -Message "Temporary files preserved for debugging at $TempDir"
    exit 1
}
# Clean up temporary directory
Remove-Item -Path $TempDir -Recurse -Force
Write-Log -Type "INFO" -Message "Cleaned up temporary directory: $TempDir"
Write-Log -Type "INFO" -Message "Successfully created combined PDF: $OutputPDF"
Write-Log -Type "INFO" -Message "Total PDFs processed: $($FooterPDFs.Count)"
Write-Log -Type "INFO" -Message "Script completed successfully!"