Ruling the continuous integration seas with Sitecore.Ship - Part 2: fileupload

One does not simply make a proper http request

Deploying Sitecore items with Sitecore.Ship

As I mentioned in the previous post Sitecore.Ship can be used to install Sitecore update or zip packages by posting HTTP requests to the Sitecore site.


I forgot to talk about the configuration in the previous post so let's have a look at that now. The Sitecore.Ship configuration is split into two parts:

  • The ship.config file (located in App_Config\Include) contains the patched IgnoreUrlPrefixes attribute to include the /services/ url part which Sitecore.Ship is using.
  • The web.config is updated with a packageInstallation element. The default values of this element are:
    <packageInstallation enabled="true" allowRemote="false" allowPackageStreaming="false" recordInstallationHistory="false" />
    The attributes are pretty self explanatory. I'll get to the recordInstallationHistory in a later post. Just make sure it is false otherwise there will be errors about a missing PackageId.

Uploading and installing a package

One of the most useful commands of Sitecore.Ship is fileupload. When you issue an HTTP request to <website>/services/package/install/fileupload you can upload and install a Sitecore package.

The wiki describes that you need to provide the path of the package as form-data in the request. Lets have look how that is done exactly.


The easiest way to test the commands Sitecore.Ship offers is to use an HTTP/REST client such as Postman, which I'm using here.

  • Once you've started Postman you'll see this screen:
    Postman startup screen
  • Change the following fields to do a post request to upload and install a package:
    Postman fileupload post request
  • Note that the value of the Key parameter (path in this example) is actually irrelevant, it can be any value.
  • Once the value type is set to File an Open file dialog can be used to select the file to upload.
  • Now press the blue Send button to do the post request. If everything went well output shows the Sitecore IDs and path of the items that were in the package and have been installed:
    Postman fileupload response

So far so good, but we don't want to use Postman manually in order to upload and install packages for every deployment right? We need a solution that can be automated and used in a continuous integration setup.


I first looked into PowerShell and the Invoke-RestMethod command but it appeared that OOTB this method does not support multipart form data, which is required to call the fileupload command. There is a workaround to create the required multipart boundaries in the request but I did not like this approach. I looked for another solution and found cURL.

cURL is a very powerful commandline application to script HTTP jobs. Getting the syntax right can be a little tricky although there is quite some documentation. Luckily Postman can generate various scripts including one for cURL:

Postman generate code

Postman cURL code

However the cURL script in the screenshot above contains a lot of unncessesary statements and actually gives errors. I've found that this is the minimal cURL syntax which works for me:

curl -F "path=@<path to update or zip package>" 'http://<website>/services/package/install/fileupload'

Sofar we've only replaced Postman with cURL, but since cURL is a commandline tool it can be easily called from a script during a deployment process as we'll see next.


In the Gist below you can see the deploy-sitecorepackage.ps1 script which I use to upload and deploy Sitecore packages. I actually prefer to use the more verbose cURL syntax (e.g. --form instead of -F) because I believe the intention of the script is much more clear to the reader who might not know the syntax well. A full description of the parameters can be found in the cURL manual.


    This function uploads & installs the specified Sitecore update package to the given $SiteUrl.
    It uses cURL ( to post a request to a Sitecore website which has Sitecore Ship installed.

    Example usage: 
    .\deploy-sitecorepackage.ps1 "C:\Project\Build\Artifacts\1-mysite-templates.update" 60 300

    [Parameter(Position=0, Mandatory=$true)]
    [Parameter(Position=1, Mandatory=$true)]
    [ValidateRange(0, 99999)]
    [int]$ConnectionTimeOutInSeconds = 300,
    [ValidateRange(0, 99999)]
    [int]$MaxTimeOutInSeconds = 900

$fileUploadUrl = "$SiteUrl/services/package/install/fileupload"
$curlPath = .\get-curlpath.ps1
$curlCommand= "$curlPath --show-error --silent --connect-timeout $ConnectionTimeOutInSeconds --max-time $MaxTimeOutInSeconds --form ""filename=@$UpdatePackagePath"" $fileUploadUrl"

Write-Output "INFO: Starting Invoke-Expression: $curlCommand"

Invoke-Expression $curlCommand

The deploy-sitecorepackage.ps1 script uses another script called get-curlpath.ps1 to obtain the path to the cURL executable.


    This script returns the full path of the curl.exe.

$curlExe = 'curl.exe'
$curlPath = Resolve-Path "$PSScriptRoot\..\tools\curl-7.33.0-win64-nossl\$curlExe" # This is the path on the local dev machine.
if (-not (Test-Path $curlPath))
    # Fall-back to use curl.exe located in the same location as the script.
    if (Test-Path "$PSScriptRoot\$curlExe")
        $curlPath = "$PSScriptRoot\$curlExe"
        Write-Error "ERROR: $curlPath not found."


These PowerShell scripts can now easily be used in continuous integration & delivery tools such as Octopus Deploy or Microsoft Release Management.

What's next?

In the next post I'll explain how Sitecore.Ship can be used to record the package installation history.