PaperCut Blog

Tech & DevCoding

Boost PowerShell Performance with the Public Web Services API

Boost PowerShell Performance with the Public Web Services API

TL;DR: If you have scripts that run server-command on a regular basis, then consider using the Public web services API instead. You’ll get a five to ten times speedup, and it’s more powerful! This post discusses the PowerShell environment, but the information is relevant to other scripting and programming languages as well.

Introduction

The PaperCut MF/NG public web services API is the most widely used way to access the PaperCut Application Server using a script or program and it’s used for a wide variety of automation and integration purposes,

There are two ways to access the public API:

This blog post explains some of the differences between the two and why you would use one over the other.

server-command is a useful tool, but does suffer a number of limitations.

  1. When running multiple commands, performance is slow because each command results in the creation of a Java virtual machine to make the API connection (server-command is written in Java)

  2. The utility only runs on the PaperCut application server system and you can’t copy the binary to another system. There are some workarounds, but they are not very elegant in my opinion.

  3. Because of the limitations of the command line when passing complex parameters, not all API calls can be used via server-command (e.g. setUserProperties() — not to be confused with setUserProperty())

  4. server-command must be run under elevated privileges (user papercut on Linux or Administrator on Windows).

Using the XML-RPC based public web services API overcomes all of the limitations listed above.

  1. It runs approximately 5-10 times faster than server-command

  2. You can connect from any machine to the application server, although extra security setup is required

  3. You can get access to all the API calls and features, for example generateAdHocReport()

  4. The public API client program can run as any user. Security comes from the fact that the client must provide a secure secret on each API call and the client IP address must be whitelisted in the PaperCut MF/NG server.

The downside is that it’s more complex to use and you will need to write a client that can make XML-RPC calls to the server. However, for Java and .NET languages we provide a proxy to do some of the heavy lifting for you and PowerShell can also use the .NET support.

Examples

To see the difference in performance let’s look at some tests I ran on my laptop. Here I am creating 100 new internal user accounts:

  • Using server-command: 2.1 minutes
  • Calling the public API: 0.14 minutes

The commands used to generate these numbers were:

measure-command {1..100 | ForEach-Object {pc-sc add-new-internal-user server-command-$_ password}}

and

measure-command {1..100 | ForEach-Object {$s.AddNewInternalUser("web-api-$_", "password","","","","")}}

For background information on how to use server-command in PowerShell see Handy PowerShell hacks for PaperCut MF/NG and on calling the public API from PowerShell refer to Administering PaperCut with PowerShell.

server-command does have the benefit that it’s simple to use and does not need much setup. For example, here’s a PowerShell script that swaps the primary and secondary card number for ALL users in the PaperCut system:

pc-sc list-user-accounts | Foreach-Object {

    $primary=(pc-sc get-user-property $_ "secondary-card-number")
    $secondary=(pc-sc get-user-property $_ "primary-card-number")

    pc-sc set-user-property $_ "primary-card-number" '"'$primary'"'
    pc-sc set-user-property $_ "secondary-card-number" '"'$secondary'"'
}

Five lines of code, plus the function definition (not shown), so super quick to write if you are used to how server-command and PowerShell work. The only really tricky piece is the special quoting in case a card number is empty.

Handy Hint: As an aside, server-command is a great way to configure PaperCut MF/NG via the set-config sub-command.

Here is the same card swapping example again, but using the public API instead of server-command:

# For dll set-up see
# https://www.papercut.com/kb/Main/AdministeringPaperCutWithPowerShell

#Import the dlls we need
Add-Type -Path "$env:USERPROFILE\.nuget\packages\kveer.xmlrpc\1.1.1\lib\netstandard2.0\Kveer.XmlRPC.dll"
Add-Type -Path "$PWD\ServerCommandProxy\bin\Release\netstandard2.0\ServerCommandProxy.dll"

$papercuthost = "localhost" # If not localhost then this address will
                            # need to be whitelisted in PaperCut

$auth = "token" # Value defined in advanced config property "auth.webservices.auth-token". Should be random

# Proxy object to call PaperCut Server API

$s = New-Object PaperCut.ServerCommandProxy($papercuthost, 9191, $auth);

$BATCH_SIZE = 100

$(do {

    [array]$userList = $s.ListUserAccounts($intCounter, $BATCH_SIZE)

    Write-Output $userList

    $intCounter += $BATCH_SIZE;

} while ($userList.Length -eq $BATCH_SIZE) ) | ForEach-Object {

    $primary = $s.GetUserProperty($_, "secondary-card-number");
    $secondary = $s.GetUserProperty($_, "primary-card-number");

    $s.SetUserProperty($_, "primary-card-number", $primary);
    $s.SetUserProperty($_, "secondary-card-number", $secondary);
}

As you can see, this example is more complicated. Additional code is needed to:

  1. Set up the DLLs and connection details
  2. Use a loop to get the usernames in chunks (The API is designed not to return a potential list of hundreds of thousands of users)

But the performance gain is huge. For a few hundred users (assuming we only need to run this once) then the server-command option probably makes more sense, but for a few thousand users (or for a process we need to run regularly), using the public API may be the only realistic option.

As I mentioned previously, using the public API gives you access to features that you can’t use with server-command. For example the public API has additional methods getUserProperties() and setUserProperties(), which we can use in the main loop process as follows:

...| ForEach-Object {

        $cardNumbers = $s.GetUserProperties($_, @("secondary-card-number","primary-card-number"))
        $s.SetUserProperties($_, @(@("primary-card-number", $cardNumbers[0]), @("secondary-card-number", $cardNumbers[1])))
}

This is obviously even more efficient (each user account now only needs two API calls, not four). It’s harder to write, but again for large number of users, or jobs that run frequently, it’s often worth the extra work.

Using the Server Command Proxy class

In the introduction I mentioned the public API was XML-RPC based, but in the above examples we have not used any XML-RPC calls. This is because in .NET and Java based environments PaperCut Software provide a proxy class that hides the low-level details. For instance, if we wanted to implement the card swapping in Python then the code would need to make XML-RPC calls directly, something like this:

#!/usr/bin/env python3

import xmlrpc.client
import sys

auth="token"  # Value defined in advanced config property "auth.webservices.auth-token". Should be random

proxy = xmlrpc.client.ServerProxy(http://localhost:9191/rpc/api/xmlrpc, verbose=False)

offset = 0

limit = 100 # Max number of usernames to retrieve on each call

while True:
    try:
        userList = proxy.api.listUserAccounts(auth, offset,limit)
    except:
        print("\nSomething went wrong. Return fault is {}".format(sys.exc_info()[0]))
        sys.exit(1)

    for user in userList:
        try:
            primary = proxy.api.getUserProperty(auth, user, "secondary-card-number")
            secondary = proxy.api.getUserProperty(auth, user, "primary-card-number")
            proxy.api.setUserProperty(auth, user, "primary-card-number", primary)
            proxy.api.setUserProperty(auth, user, "secondary-card-number", secondary)

        except:
            print("\nSomething went wrong. Return fault is {}".format(sys.exc_info()[0]))
            sys.exit(1)

    if len(userList) < limit:
        break # We have reached the end

    offset += limit # We need to next slice of users

Note that this code has been simplified, the full version can be found on GitHub.

The C# proxy class will also work on macOS or Linux, and should support any language on the .NET Core runtime.

Conclusion

So for one-off scripts that aren’t too long, use server-command, but for things that you run regularly (or would take a long time to run) use the API.

Further reading

More information about various API, server-command and related topics are listed here:

Comments