Categories: PowerShell

PowerShell: ConnectWise Documents API, Uploading a document or attachment to a ticket

Phew, this one took a minute to figure out. ConnectWise has a form based documents API (technically not really API, but it’s the way you get yourself a document into a CW ticket). First is the really amazing documentation that CW provides around the documents API

Second is then working with PowerShell to handle streamed encoding correctly, build a multipart form data payload, and then getting it to actually send the correct thing. Ultimately there were some good learning steps here. Mainly on how to construct a proper content type of “multipart/form-data”. I’m writing this in hopes that many of you that are out there that are facing a similar challenge on getting documents to upload into CW via PowerShell aren’t faced with the same 2 day challenge I just had.

Encoding Issues

Mainly the Encoding Issues were around reading a file into PowerShell and then using Invoke-RestMethod to send it off. Typically you’d work in UTF-8, while that’s great in PS when working, sending that encoding via the Invoke-RestMethod seems to break things a little and none of the characters are correct, thus resulting in a data stream sent to your destination being garbled.

Left – Proper Data | Right – Garbled Data

I happened to stumble, and by stumble, I’ve been searching the Google masters for quite a while trying to understand why the encoding wasn’t working correctly, upon this article: https://social.technet.microsoft.com/Forums/en-US/26f6a32e-e0e0-48f8-b777-06c331883555/invokewebrequest-encoding?forum=winserverpowershell

which nicely pointed me here:
https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/13685217-invoke-restmethod-and-invoke-webrequest-encoding-b

Taking from this, I modified the following from:

$fileEnc = [System.Text.Encoding]::GetEncoding('UTF-8').GetString($fileBytes);

To using the ISO 8859-1 encoding type of 28591. Converting this line to:

$fileEnc = [System.Text.Encoding]::GetEncoding(28591).GetString($fileBytes);

The rest of the time was learning to deal with boundaries in a multipart/form-data payload. Essentially finding this article:
https://gist.github.com/weipah/19bfdb14aab253e3f109

This taught me a bit about the boundaries that need to be set and more-so having to use “`r`n” in different places, you’ll see this referenced the same way as in the link in my script below using the “$LF” variable.

Enjoy, here’s the full code layout:

###INITIALIZATIONS###
$global:CWcompany    = "xxxcompanyname"
$global:CWprivate    = "xxxprivatekey"
$global:CWpublic     = "xxxpublickey"
$global:CWserver     = "https://na.myconnectwise.net/v4_6_release/apis/3.0/system/documents"
##don't use the api- url here for the server##
###CW AUTH STRING###
[string]$Authstring  = $CWcompany + '+' + $CWpublic + ':' + $CWprivate
$encodedAuth         = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(($Authstring)));

###CW HEADERS###
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Authorization", "Basic $encodedAuth")

$FilePath = 'c:\users\Tom\Desktop\image001.jpg'
$fileBytes = [System.IO.File]::ReadAllBytes($FilePath);
$fileEnc = [System.Text.Encoding]::GetEncoding(28591).GetString($fileBytes);
$boundary = [System.Guid]::NewGuid().ToString(); 
$LF = "`r`n";

$bodyLines = ( 
    "--$boundary",
    "Content-Disposition: form-data; name=`"recordType`"$LF",
    "Ticket",
    "--$boundary",
    "Content-Disposition: form-data; name=`"recordId`"$LF",
    "6956",
    "--$boundary",
    "Content-Disposition: form-data; name=`"Title`"$LF",
    "testingFINAL",
    "--$boundary",
    "Content-Disposition: form-data; name=`"file`"; filename=`"image001.jpg`"",
    "Content-Type: application/octet-stream$LF",
    $fileEnc,
    "--$boundary--$LF" 
) -join $LF

Invoke-RestMethod -Uri $CWserver -Method Post -ContentType "multipart/form-data; boundary=`"$boundary`"" -Body $bodyLines -Headers $headers
Tom Lasswell

Share
Published by
Tom Lasswell

Recent Posts

Autotask: PowerShell: Enable Client Portal for all users

This is a quick one, it's been forever since I've posted here. After moving back…

2 years ago

PowerShell :: Get Exchange Mailboxes Over XXGB

Simple command turned crazy. I ended up coming up with this due to the fact…

3 years ago

PowerShell: ConnectWise REST API Query Contacts by Email Address

I've found myself at a new job, recreating many of the processes that I spent…

5 years ago

First post in a long time — changing hosting providers

Wow, it's been a while since I've done a real post on this site. I've…

6 years ago

Powershell: AutoTask – Get Picklist Values

When using AutoTask's API it's required to lookup a various amount of picklist values that…

9 years ago

PowerShell – NetApp Gather Volume Information

This is a simple script to gather volume information including dedupe schedule and autogrow settings.…

10 years ago