Creating Invoices With PowerShell
One of things that bugs me is when I have to do something outside of PowerShell. It make me sad when I have to physically stand in line at the Post Office, or when I’ve got to depend on some magic software to do my taxes, or even when I’ve got to change the oil on my car. Why can’t PowerShell do those things? Well, I can now officially mark one thing off my long list of things I couldn’t do with PowerShell: generate an invoice! Check it out.
How to generate an invoice using PowerShell
Oh yeah, you read that right. Most folks that take care of invoicing don’t know PowerShell, so why is this useful? Because I know PowerShell and I have to invoice! See, makes perfect sense!
Data source
So before you can generate invoices, you first need to define a data source. This is where you pull your client information and your billable items from. In my case, my work is generally hourly and I track it in a helpdesk software. Can you guess how I pull that out? Yep, PowerShell. But I don’t like depending on a vendor specific platform for this kind of thing, so I’m in the process of setting up a sync from my helpdesk to a database platform. (And yes this database platform has a module for PowerShell). I’m not getting vendor specific here because that isn’t important to this post.
Moral of the section: have a place to get data from with PowerShell.
Way to make PDFs
Last year I made the resolution to start using an online platform for tracking my business finances and that crashed and burned. Hardcore. So I hired a bookkeeper and now I’m happy, ecept that I still need to do invoicing. When my bookkeeper recommended $10/mo for QB online, I scoffed and decided to spend more than $10 of my time to figure out how to do it in PowerShell, because that is clearly the right thing to do!
Anyways, lets focus on the cool part of this post: Generating the PDF. So I found an online platform with an API for creating PDFs and wrote a PowerShell Module. Essentially it involves creating a template with all the data you want in it and then sending it some json, it responds with either a url or base64 data which are both the pdf version of your data! Cool!
How it works
To authenticate to this REST based API, you need to first register for an account and get 3 distinct values: your key, secret, and workspace. For this platfrom (which this isn’t a review or endorsment for, by the way) the default workspace is just your email address and the key and secret are provided in the Dashboard -> Account Settings.
The barebones
With these values, you simply pass them as headers, and here’s a nice curl example from their docs:
curl -H "X-Auth-Key: 61e5f04ca1794253ed17e6bb986c1702" \
-H "X-Auth-Secret: 68db1902ad1bb26d34b3f597488b9b28" \
-H "X-Auth-Workspace: demo.example@actualreports.com" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-X POST -d '{"id":304355781,"name":"#1014A","number":15,"note":"Customer Notes","shipping_address":{"name":"John Smith","address":"St Patrick Road 4","city":"London","country":"United Kingdom","zip":"UK12991"}}' \
'https://us1.pdfgeneratorapi.com/api/v3/templates/21661/output'
Wait, what? How does that translate to PowerShell? Welcome to developing PowerShell modules for APIs. Where everything is curl this and PHP that.
So in PowerShell, that translates as:
$headers = @{
'X-Auth-Key' = '61e5f04ca1794253ed17e6bb986c1702'
'X-Auth-Secret' = '68db1902ad1bb26d34b3f597488b9b28'
'X-Auth-Workspace' = 'demo.example@actualreports.com'
'Content-Type' = 'application/json'
'Accept' = 'application/json'
}
$body = @{
"id" = 304355781
"name" ="#1014A"
"number" = 15
"note" = "Customer Notes"
"shipping_address" = @{
"name" = "John Smith"
"address" = "St Patrick Road 4"
"city" = "London"
"country" = "United Kingdom"
"zip" = "UK12991"
}
}
Invoke-RestMethod -Uri 'https://us1.pdfgeneratorapi.com/api/v3/templates/21661/output' -Headers $headers -Body ($body | ConvertTo-Json)
And there you go! If you thought that was just begging to have a module written, then you are definitely in the right place.
The module
Of course be sure to check out all the code for this module in my repo: https://github.com/ThePoShWolf/PS_PDFGeneratorAPI
Auth
What you saw above, that all takes place in the Invoke-PDFGeneratorAPICall
cmdlet. That cmdlet which still has parameters for the 3 auth strings, but once you create your credentials:
New-PDFGenAuthConfig -Key 'key' -Secret 'secret' -workspace 'workspace'
That will generate a module-scoped variable that stores that information and makes it available to the other cmdlets.
Templates
So when you need to list your available templates:
Get-PDFGenTemplates
id : 21661
name : Packing Slip Example
modified : 2018-10-09 20:24:12
owner : False
tags : {invoice, example}
id : 21648
name : Invoice Green Example
modified : 2018-08-20 10:53:55
owner : False
tags : {invoice, example}
id : 21650
name : Invoice Blue Example
modified : 2017-11-28 10:13:32
owner : False
tags : {invoice, example}
You’ll notice that you don’t have to specify your credentials.
Data formatting
These templates are the predefined PDFs that accept the json data passed. Here is the example invoice:
Anything in {}
is base level data that needs to be passed and anything separated by ::
is some child data. As an example, here is how the {Name}
and {BillAddr::City}
is expressed in PowerShell before converting it to json:
$body = @{
Name = 'ThePoShWolf'
BillAddr = @{
City = 'Eugene'
}
}
And something else that is cool, is that even though the template only has a single line for an item, it actually takes an array of those objects. That would be expressed in PowerShell like this (notice the ()
and {}
):
$body = @{
Name = 'ThePoShWolf'
BillAddr = @{
City = 'Eugene'
}
Line = @(
@{
Name = 'Line1'
Description = 'This will appear on Line1'
},
@{
Name = 'Line2'
Description = 'This will appear on Line2'
}
)
}
Full example
So with that information, we can pull data from our data source, format it properly and then the module handles the rest.
I’ve taken the liberty of building a custom template for my own use, here’s the current revision of it (its a work in progress):
Here’s some example data that I want to merge:
$data = @{
InvoiceNum = '1122334455'
InvoiceDate = (Get-Date).ToShortDateString()
DueDate = (Get-Date).AddDays(30).ToShortDateString()
Client = @{
Name = 'ThePoShWolf'
BillContact = 'Anthony Howell'
BillAddrLine1 = '541 Willamette St'
BillAddrLine2 = 'Ste 407B'
BillCity = 'Eugene'
BillState = 'OR'
BillZip = '97401'
Rate = '$125'
BillEmail = 'anthony@howell-it.com'
}
Line = @(
@{
Date = '01/17/2019'
Notes = 'Ticket #143 - Ticket notes'
LineQty = '2'
LineTotal = '$250'
},
@{
Date = '01/25/18'
Notes = 'Ticket #151 - Ticket Notes'
LineQty = '1'
LineTotal = '$125'
})
Subtotal = '$375'
Discount = '0'
Total = '$375'
}
And how to merge it using the module:
$Template = Get-PDFGenTemplates | ?{$_.Name -like 'HowellIT*'}
Get-PDFGenDocument -TemplateId $Template.id -Data $data -FilePath C:\tmp\Invoice.pdf -PassThru
And the output:
In closing
One of the big things that I didn’t cover is retrieving that data. If you want to see how I pull that data from my helpdesk, to my database, and then build the invoice from that. Let me know! Comment here or tweet at me - @theposhwolf.
Thanks for reading!
Leave a Comment