How to Use IronPrint with GitHub Copilot
A freight depot prints a carrier handover form for every trailer that leaves its bays. The depot's web portal saves each form as a filled PDF with fields for the driver, the seal number, the pallet count and hazardous goods, and a .NET service in a Visual Studio solution prints two copies of each. An assistant that edits the service has to work inside that solution, in the editor the team already uses.
GitHub Copilot offers chat and agent mode inside Visual Studio and VS Code, Copilot CLI for the terminal, and a GitHub cloud agent that answers an assigned issue with a pull request. IronPrint sends PDFs and images to a printer from .NET, with paper size, copies, resolution and form flattening set on a single PrintSettings object. Each of those Copilot surfaces reads the IronPrint rules from instruction files in the repository.
The prompts below ask Copilot for printing code built on IronPrint. Choosing printer hardware or configuring a print server is a separate task that no assistant can carry out from the repository. The flatten guide describes the IronPrint setting that these forms depend on.
QuickstartLaunch copilot from the solution folder and send the prompt. In the CLI, @ before a path attaches that file. In Visual Studio, Copilot Chat uses # for the same purpose.
Fill in @src/Depot.Printing/HandoverPrinter.cs so the depot can print its handover forms.
Use IronPrint 2026.10.1, the version Directory.Packages.props already pins.
Give the class three public static members:
- InstalledPrinters() returns the names from Printer.GetPrinterNames.
- BuildSettings(string printerName) returns the PrintSettings that @docs/print-profile.md describes.
- PrintAsync(string pdfPath, string printerName) checks the name first, then sends the job.
An unknown name throws an ArgumentException whose message lists the installed printers.
Build the solution and fix every error.
Then list the files you changed.
The prompt names no individual setting. The print profile in docs/print-profile.md lists every one, and Flatten is among them.
The license key goes in IRONPRINT_LICENSE_KEY, set in the environment that Copilot runs in. IronPrint itself does not read that variable. In our test, License.IsLicensed stayed false with the variable set until the program assigned its value to License.LicenseKey. Other supported places for the key are listed in the license key guide.
Minimal Workflow (5 steps)
- Download the C# printing library from NuGet
- Write the IronPrint rules into .github/copilot-instructions.md plus one path-specific instructions file
- Set Flatten on every form job and check each printer name against the installed list
- Test the PrintSettings the code builds instead of sending real jobs
- Assign the next change to the cloud agent as an issue and verify the pull request against the IronPrint API reference
How do you set IronPrint conventions for Copilot?
Project-wide rules belong in .github/copilot-instructions.md. GitHub's support matrix shows support for that file in Copilot Chat on Visual Studio, VS Code and JetBrains IDEs, and for Copilot CLI, Copilot code review and the cloud agent. Visual Studio adds the file to Copilot Chat requests only when its custom instructions checkbox is selected, under GitHub, Copilot, Copilot Chat in the Options settings.
These rules apply to printing code in this repository:
- Handover forms print through IronPrint. The NuGet package and the namespace are both IronPrint.
- Add no other printing library and no System.Drawing.Printing code.
- Keep the IronPrint version that Directory.Packages.props sets. Only an upgrade task changes it.
- IRONPRINT_LICENSE_KEY holds the license key.
- IronPrint does not read that variable, so Program.cs copies its value into License.LicenseKey.
- Never print the value, and don't dump environment variables either.
- docs/print-profile.md is the print contract. Leave it as it is and suggest changes in your reply.
- https://ironsoftware.com/print/csharp/llms.txt indexes IronPrint's docs for agents.
- No printer on this machine finishes a job without someone answering a dialog.
- Don't send a print job from a session, a test or a dry run.
Printing tests follow these rules:
- Test the PrintSettings the code builds.
- A test never calls Printer.Print or Printer.PrintAsync with a printer name that exists.
The last printing rule comes from the test machine. There, Microsoft Print to PDF prompts for a file name and Adobe PDF opened a Create Adobe PDF window, so a job sent to either printer waits for a person to answer.
How do you give the printing code its own rules?
The applyTo patterns of .github/instructions/printing.instructions.md limit its rules to part of the tree, in this case the source and test projects. GitHub's CLI documentation states that the CLI adds a path-specific file to its context only for work on a file that the applyTo value matches. In our run, printing.instructions.md was the first file Copilot opened in prompt 1.
---
applyTo: "src/**,tests/**"
---
Source and test code here follow these IronPrint rules:
- Set Flatten = true for every handover form.
- With Flatten false, IronPrint sends the page with four empty boxes.
- Those boxes are Driver, SealNumber, PalletCount and Hazmat.
- Check a printer name against Printer.GetPrinterNames before a job.
- IronPrint throws nothing for a name it cannot match, even by case or a trailing space.
- It prints on the Windows default printer instead.
- GetPrinterTrays returns an empty list for an unknown name, as it does for some real printers.
- So an empty tray list cannot prove that a printer exists.
- NumberOfCopies reaches the driver as a copy count.
- The job itself holds one page per PDF page.
- With no key, Printer.Print throws an AggregateException that wraps a LicensingException.
- PrintAsync throws the LicensingException itself.
- Margins takes millimeters, in the order left, top, right, bottom.
Each of those rules was measured with IronPrint 2026.10.1. The Flatten and printer name sections below give the measurements behind the first of them.
Why do form fields print empty without Flatten?
These measurements used a simulated printer. On Windows, IronPrint sends each job through the PrintDocument call of the WPF print dialog. A hook on that call saved the pages and print ticket of every job in an XPS file, and no print queue received a job. The four handover forms went through it with the settings from the flatten guide's combined example. The results describe the page that IronPrint gives the printer driver, not paper from a physical printer.
The comparison below uses handover form HO-1001, saved with a value in every field.
- With
Flattenleft at its default of false, the Driver, Seal number, Pallet count and Hazmat boxes were empty on all four forms. Three forms carried appearance streams for their fields, and the boxes were empty on those too. - With
Flatten = true, every value printed. The form saved without appearance streams printed its values too. - Each PDF page became one image of 2479 by 3508 pixels on an ISO A4 portrait page, which is A4 at 300 DPI. The two-page form produced two pages.
- The print ticket carried a copy count of 2, while the document held one page per PDF page.
- The 10 mm margins from the example did not move the image, which filled the page from its top left corner.
Output
HO-1001 went through the hook twice, first with Flatten false and then with Flatten true. Each PDF below is the page image of one captured job, unmodified and at its original size.
HO-1001 with Flatten false, as IronPrint built the job, captured to a file in place of a printer.
HO-1001 with Flatten true, as IronPrint built the job, captured to a file in place of a printer.
The flatten guide attributes empty fields to printer drivers that skip form layers. In the capture, the values were already missing from the page before any driver received it.
How do you print a folder of forms without sending a job?
The batch job needs a dry run so that it can be tested without a printer. The dry run records the settings each form would receive, and a reviewer can compare that log with the print profile.
Turn @src/Depot.PrintJob/Program.cs into the batch job that prints forms/inbox.
It accepts two options:
- --printer <name> picks the printer for every form.
- --dry-run writes the log and sends nothing.
The job runs in this order:
1. Exit with code 2 when the printer isn't installed, and list the installed ones on the console.
2. Read every PDF in forms/inbox in file name order.
3. Get each form's settings from HandoverPrinter.BuildSettings.
4. Append a line per form to out/print-log.csv, then print the form unless it's a dry run.
Paths are relative to the solution root.
The log is semicolon-delimited, with this header line:
Form;Printer;Flatten;Copies;PaperSize;Orientation;Dpi;Grayscale
Run the job twice with --dry-run.
Use --printer "Adobe PDF" the first time and --printer "Depot Laser 2" the second.
Show me both exit codes and the log.
Adobe PDF is a printer on the test machine. Use a name from your own printer list.
What happens when a printer name does not match?
IronPrint compares PrinterName with the names Windows reports, character for character, and raises no error when the comparison fails. Captured jobs were addressed to "NoSuchPrinter 7", to a lower-case "adobe pdf" and to "Adobe PDF" followed by a space. Microsoft Print to PDF, the Windows default printer, received all three, and only the exact name went to Adobe PDF. A licensed job to the unknown name returned after 10.3 seconds without an exception.
GetPrinterTrays cannot replace the name check, since it returned an empty list for the unknown name and also for Fax and Microsoft Print to PDF. GetPrinterNames returned all four printers without a license key, so the check also works in tests that run without one.
How do you test the print settings?
Press Shift+Tab in Copilot CLI until plan mode is active, then send the prompt below.
Plan an xUnit project in tests/Depot.Printing.Tests and add it to the solution.
Hold off on code until I approve the plan.
Its tests cover these cases:
- Every setting BuildSettings fills in matches @docs/print-profile.md.
- PaperMargins and Tray stay null, so the printer's own defaults apply.
- PrintAsync refuses a name with the wrong case or a trailing space.
- That refusal names every printer InstalledPrinters() returns.
Once the plan is approved, add the tests and run dotnet test.
Then give me the pass and fail counts.
Reviewing the plan first shows how the suite will obtain a real printer name before any test depends on the machine's printer list.
How do you fix a reprint that leaves the form fields empty?
A reprint path written apart from the main service can omit a setting that the main service applies. In this scenario, a teammate's ReprintService builds its own PrintSettings without Flatten, and drivers receive reprints with empty boxes.
Drivers say reprinted handover forms have empty Driver, Seal number and Pallet count boxes.
First prints look right. The reprint code is @src/Depot.Printing/ReprintService.cs.
Work out the cause before you edit anything:
1. Compare ReprintService.BuildSettings with HandoverPrinter.BuildSettings.
2. Open IronPrint.xml inside the IronPrint package in the NuGet cache.
3. Quote its summary for the setting that explains the empty boxes.
Then fix ReprintService.cs, and keep a reprint at one copy.
Leave the other source files alone.
Add a test for the reprint settings and run dotnet test.
Tell me the cause you found and the test result.
The shipped IronPrint.xml connects the setting to the symptom in one sentence, which describes Flatten as useful for displaying form field values. It does not state that the values are missing without it.
What makes an issue the cloud agent can act on?
Copilot receives the issue title, description and existing comments when the issue is assigned, along with any text in the optional prompt field. GitHub's documentation states that the agent is unaware of comments posted after assignment, so the issue body has to state when the work is complete.
Title: Log the printer each reprint was sent to
The work involves these files and settings:
- Files: src/Depot.Printing/ReprintService.cs, tests/Depot.Printing.Tests
- IronPrint version: unchanged from Directory.Packages.props
- Framework: net9.0-windows
After the change, reprints behave like this:
- Every reprint appends a line to out/reprint-log.csv.
- The line holds the form, the printer name, the time and the copy count.
- A printer name that Windows does not report stops the reprint before anything is sent.
Today, reprints leave no record, and ReprintAsync sends a job to any name it receives.
Keep to these limits:
- Keep the ReprintAsync signature.
- Follow .github/instructions/printing.instructions.md.
- Add no packages.
Acceptance means these checks pass:
- Every existing test passes.
- A new test passes "adobe pdf" in lower case and expects an ArgumentException before any job.
Because the test uses a name that differs from the installed printer only by case, a check that ignores case fails it.
Why do the cloud agent's tests fail on an IronPrint project?
Unless the repository configures another runner, the agent's environment is Ubuntu Linux. IronPrint's product page names Windows, macOS, iOS and Android and does not name Linux, and every project in this solution targets net9.0-windows. GitHub supports Windows 64-bit runners for the cloud agent. Its integrated firewall is not compatible with Windows, so GitHub recommends self-hosted runners or larger GitHub-hosted runners with Azure private networking for that case.
Without an Agents secret, that environment holds no license key either. In the probe, every unlicensed print call threw "Production License Required" within 0.2 seconds, before any job was created.
The copilot-setup-steps job in .github/workflows/copilot-setup-steps.yml runs before the agent starts work, and GitHub triggers it only after the default branch contains the file.
The cloud agent has to build this solution, so add a setup workflow for it.
Its path is .github/workflows/copilot-setup-steps.yml.
Copilot looks for one job, and it has to be named copilot-setup-steps.
The job works like this:
- Run it on windows-latest, because every project targets net9.0-windows.
- Its only permission is contents: read.
- Start it from workflow_dispatch and from pull requests that edit this workflow file.
- Its steps are a checkout, a .NET 9 SDK install, a restore and a Release build.
Printing calls need IRONPRINT_LICENSE_KEY, which is stored as an Agents secret.
The job sees that secret as an environment variable.
Add a step that tests whether the key is set:
- For pull_request and workflow_dispatch events, a missing key is a warning and the step passes.
- For every other event, a missing key fails the step with a message that names the secret.
That step goes last, since Copilot skips every setup step after one that fails.
Create the file and show me the YAML.
Use an Agents secret for the key: the cloud agent cannot read Actions, Codespaces or Dependabot secrets. Add IRONPRINT_LICENSE_KEY on the Agents page of Secrets and variables, found in the Security group of the repository's Settings. The agent then gets the key as an environment variable.
What should you check before merging?
Send the review as its own copilot -p command. Without --continue or --resume, that command starts a new session with no history from the coding session.
/review the changes made since this branch's first commit.
Compare each IronPrint call with .github/instructions/printing.instructions.md.
Then compare it with the IronPrint.xml that ships in the IronPrint NuGet package.
Name every place where the code and those two sources disagree.
Flag any of these as well:
- a PrintSettings for a handover form that leaves Flatten unset or false
- a print call whose printer name was never checked against Printer.GetPrinterNames
- a test that sends a real print job
- a catch block that would miss the LicensingException a print call throws
- any line that writes IRONPRINT_LICENSE_KEY to the console or a log
Report what you find and leave every file unchanged.
What did the prompts produce in a real run?
The six prompts were scheduled to run one after another in Copilot CLI, with no person at the keyboard, on a depot solution built for this guide. After each prompt, the commit was rebuilt and the batch job run on it. The settings for each form then went through IronPrint with the print call intercepted, so no job reached a printer.
- Before the run, HandoverPrinter was an empty static class and the batch job was a stub that exited with code 1.
- The first prompt changed only HandoverPrinter.cs.
- After the first prompt, BuildSettings returned Flatten on, 2 copies, A4, portrait, 300 DPI and grayscale.
- Tray and margins stayed unset, so the printer defaults applied.
- PrintAsync checked the printer name with an ordinal comparison, which is case-sensitive.
- Every field value appeared on the captured pages of all four forms.
- The print ticket for each form carried 2 copies, ISO A4 portrait, monochrome output and 300 DPI.
- The captured job for the two-page form held two pages, and each other job held one.
- The solution built with 0 errors after each of the two prompts.
- After the second prompt, the dry run for Adobe PDF exited with code 0 and logged one line per form.
- Each logged line matched the print profile, with Flatten and Grayscale both True.
- The dry run for Depot Laser 2 exited with code 2, and its console output named the four installed printers.
- Neither dry run made a print call.
- No print job appeared on any queue during the run.
- Some steps ran differently from the page. The monthly Copilot quota ran out in the second prompt, after Copilot built the job and before its dry runs. Both dry runs were run outside Copilot instead. Prompts 3 to 6 did not run, so plan mode, the reprint fix, the setup workflow and the review have no run results on this page. The issue, the cloud agent and the Agents secret require a hosted repository, so none of them was tested.
Cursor also scopes its rules to file patterns, and Claude Code likewise waits for an approved plan before any code changes. The shared run record holds the CLI version, the model chosen by Auto, the total run time and the hardware.
Troubleshooting
- How to Flatten PDFs Before Printing for form fields that print empty
- How to Get Printer Names for the names Windows reports
- How to Specify the Printer Name for jobs that reach the wrong printer
- Print() Method Hangs in WPF and WinForms for a print call that freezes the interface
- Applying License Keys for every place the key can be set
Questions?
If you have any questions, reach out to support@ironsoftware.com
Curtis Chau holds a Bachelor’s degree in Computer Science (Carleton University) and specializes in front-end development with expertise in Node.js, TypeScript, JavaScript, and React. Passionate about crafting intuitive and aesthetically pleasing user interfaces, Curtis enjoys working with modern frameworks and creating well-structured, visually appealing manuals.