Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Question
Friday, June 14, 2019 2:12 PM
I have an automated process that generates various reports for security audits in various file formats (depending on the source of the data). I'm trying to automate printing these reports to PDF as this is the format required by the security team and the auditors.
Here's my ConvertTo-PDF function, which I adopted from a blog post from a few years ago. Both the unmodified code and my updated function just print a blank 1KB PDF file. I feel like there is a missing step in here somewhere. Any help would be appreciated.
Function ConvertTo-PDF{
<#
.NOTES
Created by Adam Gloyd on 6/12/2019
Adapted from a TechNet blog post here: https://social.technet.microsoft.com/Forums/ie/en-US/04ddfe8c-a07f-4d9b-afd6-04b147f59e28/automating-printing-to-pdf
.SYNOPSIS
Converts a specified file to a PDF using the Microsoft Print to PDF file printer.
.DESCRIPTION
Uses the built-in Microsoft Print to PDF file printer to convert virtually any file to a PDF.
Be aware that if the source file is wider than the standard portrait document, the resulting PDF may not be properly formatted.
Currently, the function automatically saves the PDF in the same folder as the source document with the same name, simply updating the file extension to 'PDF.'
.Parameter File
The source file that needs to be converted to a PDF.
.Example
ConvertTo-PDF -File "C:\Test\MyWordDoc.docx"
This command will print the specified word document to a PDF called MyWordDoc.pdf stored in the 'C:\Test' directory.
.LINK
https://social.technet.microsoft.com/Forums/ie/en-US/04ddfe8c-a07f-4d9b-afd6-04b147f59e28/automating-printing-to-pdf
.INPUTS
System.String
.OUTPUTS
None
#>
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true,Position=0)]
[String]$File
)
# Import System.Drawing namespace; testing whether the module definition can load this assembly instead of placing it inside the function.
Add-Type -AssemblyName System.Drawing
# Create a new PrintDocument object
$ObjDoc = New-Object System.Drawing.Printing.PrintDocument
# Set the document display name to the specified document path
$ObjDoc.DocumentName = $File
# Add a PrinterSettings object to the existing print document object
$ObjDoc.PrinterSettings = new-Object System.Drawing.Printing.PrinterSettings
# Specify the name of the printer (in this case, the 'Microsoft Print to PDF' file printer)
$ObjDoc.PrinterSettings.PrinterName = 'Microsoft Print to PDF'
# Specify that we are printing to a file and not to a physical printer
$ObjDoc.PrinterSettings.PrintToFile = $true
# We need to set the name and path of the destination pdf file, which we will base on the name and location of the source document.
$source = Get-Item -Path $File # Create an object containing information about the source file
$pdf = Join-Path -Path ($source.DirectoryName) -ChildPath ($source.BaseName + '.pdf') # Create a new object with an updated path
# The pdf will be stored in the same location as the base file
# Update the destination PDF file name/path based on previous section
$ObjDoc.PrinterSettings.PrintFileName = $pdf
$ObjDoc.Print() # Print the document (creating the PDF file)
$ObjDoc.Dispose() # Release all related resources so the garbage collector can release associated memory.
}
All replies (4)
Wednesday, June 19, 2019 12:57 AM ✅Answered
Sorry for the slow reply. I rethought my approach based on a blog post that I found (btw, I couldn't get your first post to run). I now have a function that works (depending on source file type), which is part of a module that should be on GitHub soon.
Function ConvertTo-PDF{
<#
.NOTES
2019/06/17: Created by Adam Gloyd; inspired by code found on the Idera community website. Link listed in the links section of help.
2019/06/18: Added debug output
2019/06/18: Used debug output to fix the last couple of symantic errors
2019/06/18: Added help documentation
.SYNOPSIS
Converts the specified file to a PDF file.
.DESCRIPTION
Converts the specified file to a PDF file by printing it using the Microsoft Print to PDF driver. The new file will be in same path as the original and keep the
original name, just with a new .pdf extension. Only certain file types are supported at this time as each file type needs to be handled differently, usually by
an application designed to manage the file type.
The following file types are currently supported and are treated as raw text:
.txt
.log
.csv
.xml
The following file types will have added support in the future by using the appropriate application to handle the content and send it to the printer:
.html
.xhtml
.doc
.docx
.xls
.xlsx
Csv files are currently handled via Import-Csv, so they are not printed in a tabular format.
In the future, an option will be added to treat CSVs as an Excel document and print them using Excel.
Another option in the future may be added to take PowerShell output via the pipeline and print it to a PDF.
.EXAMPLE
ConnvertTo-PDF -File C:\Test\Test.txt
Prints the text contained within Test.txt to a PDF file: C:\Test\Test.pdf
.EXAMPLE
Get-ADGroupMember -Identity 'Domain Admins' | Out-File C:\Test\DomainAdmins.txt
ConvertTo-PDF -File C:\Test\DomainAdmins.txt
Exports a list of all of the accounts in the domain admins group and saves it to a text file.
It then imports the data and prints it to a new PDF file called: C:\Test\DomainAdmins.pdf
.LINK
Get-Content
Get-Item
Import-Csv
Import-CliXml
New-PDFPrinterPort
New-PDFUnattendPrinter
Out-Printer
Remove-PDFPrinterPort
Remove-Printer
https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/sending-powershell-results-to-pdf-part-2
.INPUTS
System.String[]
.OUTPUTS
PDF File
#>
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true,Position=0)]
[String[]]$File
)
Begin{
# Get source file information
Write-Debug "ConvertTo-PDF: Starting 'Begin' block."
$Item = Get-Item $File
Write-Debug "ConvertTo-PDF: Setting variable `$Item to the file located at: '$File'"
Write-Debug ("ConvertTo-PDF: `$Item parent directory: "+ $Item.DirectoryName)
Write-Debug ("ConvertTo-PDF: `$Item file name: " + $Item.Name)
Write-Debug ("ConvertTo-PDF: `$Item full name: " + $Item.FullName)
Write-Debug ("ConvertTo-PDF: `$Item base name: " + $Item.BaseName)
Write-Debug ("ConvertTo-PDF: `$Item file extension: " + $Item.Extension)
# Create the necessary printer that will be used to print the specified file unattended.
Write-Verbose "Creting a temporary printer and printer port for unattended printing."
Write-Debug "ConvertTo-PDF: Setting variable `$PDFPrinter to string value 'UnattendPDFPrinter'"
$PDFPrinter = "UnattendPDFPrinter"
$PDFPath = $Item.DirectoryName + '\' + $Item.BaseName + '.pdf'
Write-Debug "ConvertTo-PDF: Set variable `$PDFPath to '$PDFPath'"
Write-Debug "ConvertTo-PDF: The destination PDF file will be '$PDFPath'"
Write-Debug "ConvertTo-PDF: Creating new printer port called '$PDFPath'"
New-PDFPrinterPort -FilePath $PDFPath
Write-Debug "ConvertTo-PDF: Creating new printer called '$PDFPrinter'; it will use the port called '$PDFPath'"
New-PDFUnattendPrinter -PrinterName $PDFPrinter -PortName $PDFPath
# Rationalize file information and extensions
Write-Verbose "Checking file type to determine method for conversion."
Write-Debug "ConvertTo-PDF: Creating variable `$FileType and setting to an empty string."
[String]$FileType = ""
Write-Debug ("ConvertTo-PDF: Processing switch statement for `$Item.Extension; current value: " + $Item.Extension)
Switch($Item.Extension){
'.txt' {$FileType = "Text"; Write-Debug "File type was .txt; set `$FileType to '$FileType'"}
'.log' {$FileType = "Text"; Write-Debug "File type was .log; set `$FileType to '$FileType'"}
'.html' {$FileType = "HTML"; Write-Debug "File type was .html; set `$FileType to '$FileType'"}
'.xhtml' {$FileType = "HTML"; Write-Debug "File type was .xhtml; set `$FileType to '$FileType'"}
'.csv' {$FileType = "CSV"; Write-Debug "File type was .csv; set `$FileType to '$FileType'"}
'.xml' {$FileType = "XML"; Write-Debug "File type was .xml; set `$FileType to '$FileType'"}
'.xls' {$FileType = "Excel"; Write-Debug "File type was .xls; set `$FileType to '$FileType'"}
'.xlsx' {$FileType = "Excel"; Write-Debug "File type was .xlsx; set `$FileType to '$FileType'"}
'.doc' {$FileType = "Word"; Write-Debug "File type was .doc; set `$FileType to '$FileType'"}
'.docx' {$FileType = "Word"; Write-Debug "File type was .docx; set `$FileType to '$FileType'"}
}
Write-Debug "ConvertTo-PDF: Ending 'Begin' block."
}
Process{
Write-Debug "ConvertTo-PDF: Starting 'Process' block."
# Based on file extension, import the data and send it to the temporary printer
Write-Debug "ConvertTo-PDF: Using If statements to import content based on the value of `$FileType: $FileType."
If($FileType -eq "Text"){
# The file should be treated as raw text; use Get-Content
Write-Verbose "The file is a type of text file. Importing data with Get-Content and sending to PDF printer."
Write-Debug "ConvertTo-PDF: `$FileType matched string 'Text'; importing source file with Get-Content."
Get-Content $File | Out-Printer -Name $PDFPrinter
}ElseIf($FileType -eq "HTML"){
# The file is an HTML document; use Edge to open and print the file
Write-Debug "ConvertTo-PDF: `$FileType matched string 'HTML'; file type not supported at this time."
Write-Warning "Unsupported file type. Support for the file type of the provided file will be added soon."
}ElseIf($FileType -eq "CSV"){
# The file contains comma delimited values; use Import-Csv
Write-Verbose "File is a CSV. Using Import-Csv to retrieve the data and print raw CSV entries into target PDF file."
Write-Verbose "Support will be added in the future to treat a Csv as an Excel document. If Csv should printed in a table, convert to Excel and try again."
Write-Debug "ConvertTo-PDF: `$FileType matched string 'CSV'; Using Import-Csv to import content. This means the data will be exported in raw text."
Import-Csv $File | Out-Printer -Name $PDFPrinter
}ElseIf($FileType -eq "XML"){
# The file contains XML data; use Import-CliXml
Write-Verbose "File is an XML document; attempting to retrieve data with Import-CliXml and printing raw data to target PDF file."
Try{
Write-Debug "ConvertTo-PDF: `$FileType matched string 'XML'; Using Import-CliXml to import content. This means the data will be exported in raw text."
Import-Clixml $File -ErrorAction SilentlyContinue | Out-Printer -Name $PDFPrinter
}Catch{
Write-Verbose "Failed to import the XML document. It may not be in a supported schema."
Write-Debug "ConvertTo-PDF: failed to import the XML document; it may not be in the correct schema for use with Import-CliXml; processing the XML document as text using Get-Content"
Get-Content $File -ErrorAction SilentlyContinue | Out-Printer -Name $PDFPrinter
}
}ElseIf($FileType -eq "Excel"){
# The file is an Excel workbook; use the Excel application to open and print the workbook
Write-Debug "ConvertTo-PDF: `$FileType matched string 'Excel'; file type not supported at this time."
Write-Warning "Unsupported file type. Support for the file type of the provided file will be added soon."
}ElseIf($FileType -eq "Word"){
# The file is a Word document; use the Word appliction to open and print the the document
Write-Debug "ConvertTo-PDF: `$FileType matched string 'Word'; file type not supported at this time."
Write-Warning "Unsupported file type. Support for the file type of the provided file will be added soon."
}Else{
# The file type is currently unsupported
Write-Debug "ConvertTo-PDF: `$FileType value unmatched; file type not supported at this time."
Write-Error "Unsupported file type. The file type of the provided file is not surrently supported by this converter."
}
Write-Debug "ConvertTo-PDF: Ending 'Process' block."
}
End{
Write-Debug "ConvertTo-PDF: Starting 'End' block."
# Do any post-run cleanup processes
Write-verbose "Removing temporary PDF printer."
Write-Debug "ConvertTo-PDF: Running 'Remove-Printer -Name $PDFPrinter -Confirm:`$false' to delete the temporary printer."
Remove-Printer -Name $PDFPrinter -Confirm:$false
Write-Verbose "Removing temporary PDF printer port."
Write-Debug "ConvertTo-PDF: Running 'Remove-PDFPrinterPort -FilePath $PDFPath' to delete the temporary printer port."
Remove-PDFPrinterPort -FilePath $PDFPath
Write-Debug "ConvertTo-PDF: Closing 'End' block."
}
}
Friday, June 14, 2019 2:54 PM
Simple. You have to write the text into the document. What you have is a blank document with a name but no contents. Get the graphics object and use the System.Drawing API to write the text into the document.
Here is a full example of how to render a text file to a document being printed. THe output is sent one page at a time until there is no more to print.
Here is the C# example that can be run under PowerShell as a quick demo. CHange the two file names in the code to your file names and test. You will see how it works:
$code = @'
using System;
using System.IO;
using System.Drawing;
using System.Drawing.Printing;
using System.Windows.Forms;
public partial class Form1 : System.Windows.Forms.Form{
private System.ComponentModel.Container components;
private System.Windows.Forms.Button printButton;
private Font printFont;
private StreamReader streamToPrint;
public Form1(){
// The Windows Forms Designer requires the following call.
InitializeComponent();
}
// The Click event is raised when the user clicks the Print button.
private void printButton_Click(object sender, EventArgs e){
try{
streamToPrint = new StreamReader("d:\\scripts\\test.csv");
try{
printFont = new Font("Arial", 10);
PrintDocument pd = new PrintDocument();
pd.PrinterSettings.PrinterName = "Microsoft Print to PDF";
pd.PrinterSettings.PrintToFile = true;
pd.PrinterSettings.PrintFileName = "d:\\scripts\\test.pdf";
pd.PrintPage += new PrintPageEventHandler(this.pd_PrintPage);
pd.Print();
}
finally{
streamToPrint.Close();
}
}
catch (Exception ex){
MessageBox.Show(ex.Message);
}
}
// The PrintPage event is raised for each page to be printed.
private void pd_PrintPage(object sender, PrintPageEventArgs ev){
float linesPerPage = 0;
float yPos = 0;
int count = 0;
float leftMargin = ev.MarginBounds.Left;
float topMargin = ev.MarginBounds.Top;
string line = null;
// Calculate the number of lines per page.
linesPerPage = ev.MarginBounds.Height /
printFont.GetHeight(ev.Graphics);
// Print each line of the file.
while (count < linesPerPage && ((line = streamToPrint.ReadLine()) != null)){
yPos = topMargin + (count *
printFont.GetHeight(ev.Graphics));
ev.Graphics.DrawString(line, printFont, Brushes.Black,
leftMargin, yPos, new StringFormat());
count++;
}
// If more lines exist, print another page.
if (line != null)
ev.HasMorePages = true;
else
ev.HasMorePages = false;
}
// The Windows Forms Designer requires the following procedure.
private void InitializeComponent(){
this.components = new System.ComponentModel.Container();
this.printButton = new System.Windows.Forms.Button();
this.ClientSize = new System.Drawing.Size(504, 381);
this.Text = "Print Example";
printButton.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft;
printButton.Location = new System.Drawing.Point(32, 110);
printButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
printButton.TabIndex = 0;
printButton.Text = "Print the file.";
printButton.Size = new System.Drawing.Size(136, 40);
printButton.Click += new System.EventHandler(printButton_Click);
this.Controls.Add(printButton);
}
}
'@
Add-Type $code -ReferencedAssemblies System.Windows.Forms, System.Drawing
$f = [form1]::new()
$f.ShowDialog()
\(ツ)_/
Friday, June 14, 2019 6:10 PM
Just for fun - here is a more automated version that takes a source file and a pdf file name and prints it. THe Window closes automatically and there is no need to click a button.
$code = @'
using System;
using System.IO;
using System.Drawing;
using System.Drawing.Printing;
using System.Windows.Forms;
public class PrintToPDF : System.Windows.Forms.Form{
private System.ComponentModel.Container components;
private System.Windows.Forms.Button printButton;
private Font printFont;
private StreamReader streamToPrint;
private string _filePathname;
private string _pdfPathname;
public PrintToPDF(string filePathname, string pdfPathname){
_pdfPathname = pdfPathname;
_filePathname = filePathname;
InitializeComponent();
}
private void PrintToPDF_Activated(object sender, EventArgs e){
Console.Write("Form loaded!");
printButton.PerformClick();
}
// The Click event is raised when the user clicks the Print button.
private void printButton_Click(object sender, EventArgs e){
try{
streamToPrint = new StreamReader(_filePathname);
try{
printFont = new Font("Arial", 10);
PrintDocument pd = new PrintDocument();
pd.PrinterSettings.PrinterName = "Microsoft Print to PDF";
pd.PrinterSettings.PrintToFile = true;
pd.PrinterSettings.PrintFileName = _pdfPathname;
pd.PrintPage += new PrintPageEventHandler(this.pd_PrintPage);
pd.Print();
}
finally{
streamToPrint.Close();
Form f = this.FindForm();
f.Close();
}
}
catch (Exception ex){
MessageBox.Show(ex.Message);
}
}
// The PrintPage event is raised for each page to be printed.
private void pd_PrintPage(object sender, PrintPageEventArgs ev){
float linesPerPage = 0;
float yPos = 0;
int count = 0;
float leftMargin = ev.MarginBounds.Left;
float topMargin = ev.MarginBounds.Top;
string line = null;
// Calculate the number of lines per page.
linesPerPage = ev.MarginBounds.Height / printFont.GetHeight(ev.Graphics);
// Print each line of the file.
while (count < linesPerPage && ((line = streamToPrint.ReadLine()) != null)){
yPos = topMargin + (count * printFont.GetHeight(ev.Graphics));
ev.Graphics.DrawString(line, printFont, Brushes.Black, leftMargin, yPos, new StringFormat());
count++;
}
// If more lines exist, print another page.
if (line != null) ev.HasMorePages = true;
else ev.HasMorePages = false;
}
// The Windows Forms Designer requires the following procedure.
private void InitializeComponent(){
this.components = new System.ComponentModel.Container();
this.printButton = new System.Windows.Forms.Button();
this.ClientSize = new System.Drawing.Size(10, 10);
this.Text = "Print 2 PDF";
this.Visible = false;
printButton.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft;
printButton.Location = new System.Drawing.Point(1, 1);
printButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
printButton.TabIndex = 0;
printButton.Text = "Print the file.";
printButton.Size = new System.Drawing.Size(1,1);
printButton.Click += new System.EventHandler(printButton_Click);
this.Activated += new System.EventHandler(PrintToPDF_Activated);
this.Controls.Add(printButton);
}
}
'@
Add-Type $code -ReferencedAssemblies System.Windows.Forms, System.Drawing
$f = [PrintToPDF]::new('d:\scripts\test.csv', 'd:\scripts\test.pdf')
$f.ShowDialog()
\(ツ)_/
Wednesday, June 19, 2019 12:59 AM
It only handles text-type sources well currently (.txt, .log, etc), but I'm going to add support for Office documents and better HTML support at some point.