PaperCut Blog

Tech & DevCoding

Handy PowerShell hacks for PaperCut MF/NG

Handy PowerShell hacks for PaperCut MF/NG

I migrated from an Apple MacBook to a Windows 10 laptop earlier in the year. Ever since I’ve been busy grokking the world of PowerShell and .NET Core.

As this little learning project has to fit around all my other activities, it’s very much still a work in progress. But I’ve started to share some of my discoveries with PaperCut colleagues, and thought I’d also share them with you.

Note these tips assume you have some PowerShell experience. However, even if you haven’t used PowerShell , you might find it useful to cut and paste these examples into your TEST PaperCut MF/NG server system to see how they work.

There are a number of PowerShell details I’ve glossed over, so further experimentation and reading are very much encouraged.


Tip 1: Create a PowerShell function to make it easy to call the PaperCut server-command utility

Tip 2: The location of the profile is hard to remember

Tip 3: List the PaperCut MF/NG Windows services

Tip 4: Make frequent use of the Get-Member cmdlet!

Tip 5: Use the Select-Object and Where-Object cmdlets to locate and process data

Tip 6: If life gives you lemons strings, make lemonade custom objects*

Tip 7: Use server-command’s get-group-members subcommand instead of list-user-accounts


Tip 1: Create a PowerShell function to make it easy to call the PaperCut server-command utility

Add the following to your PowerShell profile:

Function server-command {
 &'C:\Program Files\PaperCut MF\server\bin\win\server-command.exe' $Args
}

Note the server-command program only runs on the PaperCut MF/NG application server.

Tip 2: The location of the profile is hard to remember

It also varies depending on which version of PowerShell and which operating system you are using. Just use the “magic” variable $PROFILE instead. For example:

code $PROFILE

(I use VS Code to edit my files)

Tip 3: List the PaperCut MF/NG Windows services

You can find a list of the running PaperCut processes with the Get-Service cmdlet as follows:

Get-Service -DisplayName *PaperCut*

The cmdlet displays a limited amount of information by default, so perhaps more usefully.

Get-Service -DisplayName *PaperCut*
  | Format-Table -Property  Name, Status, StartType

Here’s more about Format-Table

Tip 4: Make frequent use of the Get-Member cmdlet!

The biggest difference between PowerShell and other shells (for instance cmd.exe or Bash) is that PowerShell processes objects, while other shells only process text strings (technically that’s an oversimplification, but it will do for now).

This means that the type of the objects passed between PowerShell commands matters a lot! You can discover the details of an object type using the Get-Member cmdlet.

Let’s compare a couple of the previous examples, but add the Get-Member command to see the output type instead:

Get-Service |
  Where-Object -Property DisplayName -like -Value "PaperCut*" |
  Get-Member

And we get:

   TypeName: System.ServiceProcess.ServiceController
Name                      MemberType Definition
----                      ---------- ----------
Name                      AliasProperty Name = ServiceName
RequiredServices          AliasProperty RequiredServices = Services...
Disposed                  Event System.EventHandler Dispose...
Close                     Method void Close()
Continue                  Method void Continue()

But if we run the pipeline …

Get-Service -DisplayName *PaperCut* |
  Format-Table -Property  Name, Status, StartType |
  Get-Member

… the output type is very different:

   TypeName: Microsoft.PowerShell.Commands.Internal.Format.FormatStartData
Name                                    MemberType Definition
----                                    ---------- ----------
Equals                                  Method bool Equals(Syst...
GetHashCode                             Method int GetHashCode()
GetType                                 Method type GetType()
ToString                                Method string ToString()
autosizeInfo                            Property Microsoft.PowerS...
ClassId2e4f51ef21dd47e99d3c952918aff9cd Property   string ClassId2e...
groupingEntry                           Property Microsoft.PowerS...
pageFooterEntry                         Property Microsoft.PowerS...
pageHeaderEntry                         Property Microsoft.PowerS...
shapeInfo                               Property Microsoft.PowerS...


   TypeName: Microsoft.PowerShell.Commands.Internal.Format.GroupStartData

Name                                    MemberType Definition
----                                    ---------- ----------
Equals                                  Method bool Equals(Syst...
...

So Format-Table creates output suitable for display purposes and should not be used in the middle of a pipeline when data requires further processing. Which leads us to …

Tip 5: Use the Select-Object and Where-Object cmdlets to locate and process data

See the previous tip for some hints on why. Here’s an example:

Get-Service |
  Where-Object -Property DisplayName -like -Value "PaperCut*" |
  Select-Object -Property ServiceName,Status,StartType

The output from this pipeline is still a ServiceController type, which can be further processed, so, for example, we can convert it to JSON:

Get-Service |
  Where-Object -Property DisplayName -like -Value "PaperCut*" |
  Select-Object -Property ServiceName,Status,StartType |
  ConvertTo-Json

Tip 6: If life gives you lemons strings, make lemonade some custom objects*

* With apologies to Elbert Hubbard

The server-command utility usually returns strings, and often we need to make multiple calls to discover the various pieces of information we need. For example, suppose we want to create a list of users with a Boolean flag to indicate if they are internal or external user accounts. This involves using the subcommand list-user-accounts to get a list of all the user names, and then iterating over the list to get the value of the user’s internal property. Note that the property value is returned as a string (“true”/”false”) rather than a Boolean. How can we wrap this to make it more PowerShell compatible?

The “trick” is that we can convert a string “true” or “false into the corresponding Boolean value with the [System.Convert]::ToBoolean method. So to discover if a user is an internal user let’s try:

pc-sc get-user-property <user-name> internal over all the user accounts, and see what types are returned:

pc-sc list-user-accounts |
  ForEach-Object {pc-sc get-user-property $_ internal} |
  Get-Member

Gives us:

TypeName: System.String

Now use ToBoolean()  and convert strings to Boolean after each get-user-property call:

pc-sc list-user-accounts |
  ForEach-Object {[System.Convert]::ToBoolean((pc-sc get-user-property $_ internal))} |
  Get-Member

(Yes, you really do need the ((.. )) double parentheses).

True

So now I can use Foreach-Object to create a hash table of the user name and a Boolean flag for the internal status:

pc-sc list-user-accounts |
Foreach-Object {
  @{
  user=$_;
  internal=[System.Convert]::ToBoolean((pc-sc get-user-property $_ internal))
  }
}

Name                           Value
----                           -----
user                           ahmed
internal                       False
user                           guest-1
internal                       True

And we use the Get-Member command to get the type:

   TypeName: System.Collections.Hashtable

We can now take it one stage further and convert the hash table (name value pairs) into a custom PowerShell object:

pc-sc list-user-accounts |
Foreach-Object {
  [PSCustomObject]@{
  user=$_;
 internal=[System.Convert]::ToBoolean((pc-sc get-user-property $_ internal))
  }
}

user    internal
----    --------
ahmed      False
guest-1     True
guest-2     True
jane       False
john       False

And Get-Member tells us:

   TypeName: System.Management.Automation.PSCustomObject

Name        MemberType Definition
----        ---------- ----------

Equals      Method bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method type GetType()
ToString    Method string ToString()
internal    NoteProperty bool internal=False
user        NoteProperty System.String user=ahmed

So what’s the point? Well, now I can wrap it in a function and use it to solve a number of admin tasks. I’ve extended the above example into a function that returns an object for each user with properties for:

  • username
  • internal flag
  • account balance

Function get-pc-users-balance-and-account-type {
  pc-sc list-user-accounts |
    Foreach-Object {
       [PSCustomObject]@{
         user=$_;
          internal=
            [System.Convert]::ToBoolean((pc-sc get-user-property $_ internal));
          balance=
            [System.Convert]::ToSingle((pc-sc get-user-property $_ balance));
       }
      }
}

And then, for instance, I can use the new function to give an extra $5.50 of credit to all internal users who have a balance less than $5.00:

get-pc-users-balance-and-account-type |
  Where-Object {$_.internal -and $_.balance -le 5.0 } |
  Foreach-Object {
    pc-sc adjust-user-account-balance $_.user 5.5 "By Powershell";
    Write-Output "$($_.user) got an extra `$5.5"
  }

Tip 7: Use server-command’s get-group-members subcommand instead of list-user-accounts

In tip 6 we found the balance and internal flag for every user account in PaperCut MF/NG, but then only processed internal users. This is often a very small subset of all the users in the system, though. We could be wasting a lot of time retrieving information we never need.

So let’s change the example a bit:

Function get-pc-internal-users-and-balance {
  pc-sc
get-group-members "!!Internal Users!!" |
    Foreach-Object {
       [PSCustomObject]@{
         user=$_;
  internal=$True;
         balance=
           [System.Convert]::ToSingle((pc-sc get-user-property $_ balance));
      }
    }
}

Note I’ve hard-coded the internal flag …

internal=$True;

… so that the original business logic is the same:

get-pc-internal-users-and-balance |
  Where-Object {$_.internal -and $_.balance -le 5.0 } |
  Foreach-Object {
    pc-sc adjust-user-account-balance $_.user 5.5 "By Powershell";
    Write-Output "$($_.user) got an extra `$5.5"
  }

<Aaaaaand that’s enough PowerShell tips for now — Ed>

In a future post, I’ll show you how you can use the PaperCut MF/NG health API in PowerShell to get more detailed information about the state of your application server (hint, use the Invoke-RestMethod cmdlet).

Future Work: I’m currently trying to work out how to use the XML-RPC web services API directly from PowerShell via .NET Core. The current approach depends on .NET Foundation which will become a legacy framework at some point.

Comments