- Create file, for example
edit script.sh
in 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!"