├── Authentication
└── README.md
├── LICENSE
├── PrinterScripts
└── README.md
├── PublicWebServicesAPI_AND_servercommandScripts
├── C
│ ├── Makefile
│ ├── README.md
│ └── xmlrpc.c
├── InstallNewWebAuthToken
│ ├── InstallNewWebAuthToken.go
│ └── README.md
├── PrinterManagement
│ ├── ConfigureSecureFindMePrinting
│ │ ├── find-me-automation.py
│ │ └── printerList.csv
│ ├── configPrintersAndMFDsAfterFleetDeplyment
│ │ ├── app.py
│ │ ├── batch-devices.csv
│ │ └── batch-printers.csv
│ └── setAllPrinterAuthMode.py
├── README.md
├── ServerCommandProxy-dll-setup.ps1
├── Update_Card_Number
│ ├── sample.csv
│ └── update_card_numbers.pl
├── addInfoToCSVreport.py
├── allUserInDelegationGroup.sh
├── assignPrintersToServerGroups.ps1
├── assignPrintersToServerGroups.sh
├── automaticallyChargeToSingleSharedAccount
│ ├── automaticallyChargeToSingleSharedAccount.ps1
│ ├── example-csv.csv
│ └── readme.md
├── changePrinterChargingType.py
├── csvListOfPrintersAndPropertyValue.sh
├── delete-printers-matching-regex.sh
├── delete-users-matching-regex
├── delete-users-matching-regex.ps1
├── deleteUsersAfterPeriod.sh
├── displaySharedBalanceWhenAutoChargeToSharedAccount.py
├── findAllUsersInDept.py
├── findAllUsersInGroup.py
├── findAllUsersInGroupNewWay.py
├── list-shared-account-balances
├── list-shared-account-balances.ps1
├── list-user-with-no-card-no-1.ps1
├── listExpiredUsers.py
├── listGroupMembership.sh
├── listPrinterStatuses.py
├── loadSharedAccounts.py
├── lookUpUserByProperty
│ ├── README.md
│ └── getPPCuserName.sh
├── processExpiredUserAccoounts.py
├── removeTempCardsJob.py
├── removeUsersListedInFile
│ ├── removeUsersListedInFile
│ └── testData
├── selectPrinterCostModel.py
├── setAccountSelectionMode.py
├── setUserProperties.php
├── simpleRefundBalance
│ ├── simpleRefundBalance.py
│ └── views
│ │ └── promptForRefund.tpl
├── simpleTopUpBalance
│ ├── README.md
│ ├── requirements.txt
│ ├── simpleTopUpBalance.py
│ └── views
│ │ └── promptForDeposit.tpl
├── swapCardNumbers.ps1
├── swapCardNumbers.py
├── swapCardNumbers.sh
├── swapCardNumbersViaServerCommand.ps1
├── topUpOverDrawnAccounts.sh
├── useHealthAPI.ps1
└── userSelectSharedAccount
│ ├── userSelectSharedAccount.py
│ └── views
│ └── displayAccounts.tpl
├── README.md
├── Reports
└── README.md
├── TopUpCards
├── README.md
└── createTopUpCards.py
├── fastReleaseProtocol
└── NetworkCardReaderFastRelease.py
├── mf-automation
├── do-we-need-to-upgrade.ps1
└── do-we-need-to-upgrade.sh
└── print-deploy-bulk-scripts
├── README.md
├── assign_print_queues_to_zones.py
├── create_zones.py
├── printers_to_zones_example.csv
└── zones.csv
/Authentication/README.md:
--------------------------------------------------------------------------------
1 | # This material has moved to https://github.com/PaperCutSoftware/CustomSynAndAuthentication
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-2016 PaperCut Software and others
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/PrinterScripts/README.md:
--------------------------------------------------------------------------------
1 | # This material has move to https://github.com/PaperCutSoftware/PrinterScripts
2 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/C/Makefile:
--------------------------------------------------------------------------------
1 | PROJECT:=xmlrpc
2 |
3 | XMLRPC_FLAGS:=${shell xmlrpc-c-config client --cflags --libs}
4 |
5 | CFLAGS+=$(XMLRPC_FLAGS)
6 |
7 | $(PROJECT): $(PROJECT).c
8 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/C/README.md:
--------------------------------------------------------------------------------
1 | # Call the PaperCut XML-RPC web services API from C
2 |
3 | This very simple example shows how to use the xmlrpc-c
4 | library to call a PaperCut server.
5 |
6 | You will need to install the [xmlrpc-c](http://xmlrpc-c.sourceforge.net/)
7 | library from packages, or build from source.
8 |
9 | There is a sample makefile to build the code. Please pay
10 | particular attention to the set up of XMLRPC_FLAGS.
11 |
12 | Many thanks to Bryan Henderson from the xmlrpc-c project for help
13 | in getting this to build.
14 |
15 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/C/xmlrpc.c:
--------------------------------------------------------------------------------
1 | // Call the PaperCut XML-RPC web services API
2 | //
3 | // Alec Clews
4 | //
5 | // Adapted from
6 | // https://sourceforge.net/p/xmlrpc-c/code/HEAD/tree/trunk/examples/xmlrpc_sample_add_client.c
7 | //
8 |
9 |
10 | #include
11 | #include
12 | #include
13 |
14 | //#include "config.h" // supplied by your environment
15 |
16 | #define NAME "PaperCut web services API XML-RPC C Test Client"
17 | #define VERSION "0.1"
18 |
19 |
20 | static void
21 | die_if_fault_occurred(xmlrpc_env * const envP) {
22 | if (envP->fault_occurred) {
23 | fprintf(stderr, "ERROR: %s (%d)\n",
24 | envP->fault_string, envP->fault_code);
25 | exit(1);
26 | }
27 | }
28 |
29 | int
30 | main(int const argc,
31 | const char ** const argv) {
32 |
33 | if (argc != 2) {
34 | fprintf(stderr, "ERROR: Must provide user name as paramter");
35 | exit(1);
36 | }
37 |
38 | char * const auth = "token";
39 |
40 | xmlrpc_env env;
41 | xmlrpc_client * clientP;
42 | xmlrpc_value * resultP;
43 | int found;
44 | char * const url = "http://localhost:9191/rpc/api/xmlrpc";
45 |
46 | char * const methodName = "api.isUserExists";
47 |
48 | /* Initialize our error-handling environment. */
49 | xmlrpc_env_init(&env);
50 |
51 | xmlrpc_client_setup_global_const(&env);
52 |
53 | xmlrpc_client_create(&env, XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION, NULL, 0,
54 | &clientP);
55 | die_if_fault_occurred(&env);
56 |
57 | /* Make the remote procedure call */
58 | xmlrpc_client_call2f(&env, clientP, url, methodName, &resultP,
59 | "(ss)", auth, argv[1]);
60 | die_if_fault_occurred(&env);
61 |
62 | /* Get our result and print it out. */
63 | xmlrpc_read_bool(&env, resultP, &found);
64 | die_if_fault_occurred(&env);
65 | printf("The user %s %s\n", argv[1], found ? "exists" : "does not exist");
66 |
67 | /* Dispose of our result value. */
68 | xmlrpc_DECREF(resultP);
69 |
70 | /* Clean up our error-handling environment. */
71 | xmlrpc_env_clean(&env);
72 |
73 | xmlrpc_client_destroy(clientP);
74 |
75 | xmlrpc_client_teardown_global_const();
76 |
77 | return 0;
78 | }
79 |
80 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/InstallNewWebAuthToken/InstallNewWebAuthToken.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "log"
8 | "os"
9 | "os/exec"
10 | "os/user"
11 | "path/filepath"
12 | "runtime"
13 | "strings"
14 | )
15 |
16 | var serverCommandBin string
17 |
18 | // Before we do anything let's find the localtion of the server-command binary
19 | func init() {
20 |
21 | var installRoot string
22 |
23 | if runtime.GOOS == "windows" {
24 |
25 | programFiles := os.Getenv("PROGRAMFILES")
26 |
27 | installRoot := fmt.Sprintf("%s\\PaperCut MF", programFiles)
28 | if _, err := os.Stat(installRoot); err != nil {
29 |
30 | installRoot = fmt.Sprintf("%s\\PaperCut NG", programFiles)
31 |
32 | if _, err := os.Stat(fmt.Sprintf("%s\\PaperCut NG", programFiles)); err != nil {
33 | log.Fatal("PaperCut MF/NG installation not found")
34 | }
35 | }
36 |
37 | serverCommandBin = filepath.Join(installRoot, "server", "bin", "win", "server-command.exe")
38 |
39 | } else if runtime.GOOS == "linux" {
40 | if papercutAdmin, err := user.Lookup("papercut"); err != nil {
41 | log.Fatal("PaperCut not installed")
42 | } else {
43 | installRoot := papercutAdmin.HomeDir
44 | if _, err := os.Stat(installRoot); err != nil {
45 | log.Fatal("PaperCut not installed")
46 | }
47 |
48 | serverCommandBin = filepath.Join(installRoot, "server", "bin", "linux-x64", "server-command")
49 | }
50 |
51 | } else if runtime.GOOS == "darwin" {
52 | if _, err := os.Stat("/Applications/PaperCut MF"); err == nil {
53 | installRoot = "/Applications/PaperCut MF"
54 | } else if _, err := os.Stat("/Applications/PaperCut NG"); err == nil {
55 | installRoot = "/Applications/PaperCut NG"
56 | } else {
57 | log.Fatal("PaperCut not installed")
58 | }
59 | serverCommandBin = filepath.Join(installRoot, "server", "bin", "macos", "server-command")
60 | } else {
61 | log.Fatalf("Runtime platform %v not supported", runtime.GOOS)
62 | }
63 | }
64 |
65 | // Note: using server-command binary as we don't know have a value web services auth token yet
66 | func execServerCommand(args ...string) (value []byte, err error) {
67 |
68 | var out bytes.Buffer
69 |
70 | cmd := exec.Command(serverCommandBin, args...)
71 | cmd.Stdout = &out
72 | log.Printf("Running command %v and waiting for it to finish...", append([]string{serverCommandBin}, args...))
73 |
74 | err = cmd.Run()
75 | value = []byte(strings.TrimSpace(out.String()))
76 |
77 | return
78 | }
79 |
80 | func update(jsonData interface{}) {
81 | if value, err := json.Marshal(jsonData); err != nil {
82 | log.Fatalf("could not marshal %v", jsonData)
83 | } else {
84 | if output, err := execServerCommand("set-config", "auth.webservices.auth-token", string(value)); err != nil {
85 | log.Fatalf("Failed to update auth.webservices.auth-token: %v", output)
86 | }
87 | log.Printf("Updated auth.webservices.auth-token with new token value %v", value)
88 | }
89 | }
90 |
91 | func main() {
92 |
93 | var result []byte
94 | var err error
95 |
96 | if len(os.Args) != 3 {
97 | log.Fatal("Auth key and value not supplied")
98 | }
99 |
100 | tokenName := os.Args[1]
101 | securityToken := os.Args[2]
102 |
103 | log.Printf("Adding key %v, value %v", tokenName, securityToken)
104 |
105 | if result, err = execServerCommand("get-config", "auth.webservices.auth-token"); err != nil {
106 | log.Fatalf("Failed getConfig result=%v, err = %v", result, err)
107 | }
108 |
109 | if len(result) == 0 {
110 |
111 | auth := make(map[string]string)
112 | auth[tokenName] = securityToken
113 |
114 | update(auth)
115 |
116 | return
117 | }
118 |
119 | var tokensAsArray []string
120 |
121 | // Note: if the config key is not an array of strings this parse will fail
122 | if notAnArray := json.Unmarshal(result, &tokensAsArray); notAnArray == nil {
123 | log.Printf("auth.webservices.auth-token is a json array %v", tokensAsArray)
124 |
125 | for _, i := range tokensAsArray {
126 | if i == securityToken {
127 | log.Printf("Security token %v already installed", i)
128 | return
129 | }
130 | }
131 |
132 | update(append(tokensAsArray, securityToken))
133 | return
134 | }
135 |
136 | var tokensAsObject map[string]string
137 |
138 | // Note: if the config key is not a map of strings indexed by strings this parse will fail
139 | if notAnObject := json.Unmarshal(result, &tokensAsObject); notAnObject == nil {
140 | log.Printf("auth.webservices.auth-token is a json object %v", tokensAsObject)
141 |
142 | tokensAsObject[tokenName] = securityToken
143 |
144 | update(tokensAsObject)
145 |
146 | return
147 | }
148 |
149 | // Assume it's a simple string
150 | log.Printf("auth.webservices.auth-token is a simple string")
151 |
152 | tokensAsObject = make(map[string]string)
153 | tokensAsObject["default"] = string(result) // Save the old auth key as well
154 | tokensAsObject[tokenName] = securityToken
155 |
156 | update(tokensAsObject)
157 |
158 | return
159 | }
160 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/InstallNewWebAuthToken/README.md:
--------------------------------------------------------------------------------
1 | # Install a New Web Auth Token
2 |
3 | When using the web services API all calls need to provide a security token.
4 |
5 | This token needs to be configured in PaperCut via the advanced config key
6 |
7 | ```
8 | auth.webservices.auth-token
9 | ```
10 |
11 | The key can be configured using the `server-command` utility.
12 |
13 | This Go program is designed to be compiled and shipped with a third party
14 | integration so that a new security token can be installed at install
15 | time.
16 |
17 | NOTE: This program must be run on the PaperCut MF/NG server under the PaperCut or admin/root account
18 |
19 | It should work on Windows, MacOS and Linux. But you should test thoroughly as I didn't have
20 | access to MacOS or Linux.
21 |
22 | To run the program
23 |
24 | ```
25 | InstallNewWebAuthToken
26 | ```
27 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/PrinterManagement/ConfigureSecureFindMePrinting/find-me-automation.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Must be Python 3.6 or above
4 |
5 | helpText="""
6 |
7 | This script will configure a virtual find me Q for release and printing on multiple devices using find me printing.
8 |
9 | It designed to support MFD based release stations, as explained at
10 |
11 | https://www.papercut.com/support/resources/manuals/ng-mf/applicationserver/topics/device-mf-copier-integration-release-multiple-operating-systems.html
12 |
13 | Note: Needs PaperCut MF 2.0.3 or above
14 |
15 | For simplicity this script configures a single hold release queue with multiple MFD queues. It can be extended for more
16 | complex requirements
17 |
18 | A print queue must already be created that can be configured as a virtual queue. The physical print queues for each MFD must also be defined in PaperCut MF.
19 |
20 | Usage: ./find-me-automation.py [server of virtual q]\[virtual q name] [csv input files containing printer info]...
21 |
22 | CSV files must be in the following format and must NOT contain a header line
23 |
24 | ,\,...
25 | ...
26 |
27 | each line contains the A) the MFD that will run the release station and B) a list of physical printer server and queue names
28 |
29 | Note that each MFD release station can support multiple print server operating systems as described in the above documentation.
30 | """
31 |
32 | import xmlrpc.client
33 | from ssl import create_default_context, Purpose
34 | from sys import argv, exit
35 | from csv import DictReader
36 |
37 | host = "https://localhost:9192/rpc/api/xmlrpc"
38 |
39 | # Value defined in advanced config property "auth.webservices.auth-token". Should be random
40 | auth = "password"
41 |
42 | proxy = xmlrpc.client.ServerProxy( host, verbose=False,
43 | context = create_default_context(Purpose.CLIENT_AUTH))
44 |
45 | server_command=proxy.api
46 |
47 | print( "----------------------------")
48 | print( "Find Me Queue Automation")
49 | print( "----------------------------")
50 |
51 | if len(argv) < 3 :
52 | exit( f"Incorrect input params Exiting...{helpText}")
53 |
54 | split = argv[1].split("\\",1)
55 | if not len(split) == 2:
56 | exit(f"Invalid virtual queue name supplied {argv[1]}")
57 |
58 |
59 | virtual_Q_server = split[0].strip()
60 | virtual_Q_printer = split[1].strip()
61 |
62 | print( f"Configuring Printer: {virtual_Q_server}/{virtual_Q_printer} for find me release")
63 |
64 | queue_list = list()
65 | printIDlist = list()
66 |
67 | for file in argv[2:]: # Loop over each file on the command line
68 |
69 | with open(file, newline='') as csvfile:
70 |
71 | printQConfigReader = DictReader(csvfile, fieldnames=["MFD"], restkey="printQueues")
72 | for prt in printQConfigReader:
73 |
74 | print(f"{prt}")
75 | mfd_id = server_command.getPrinterProperty(auth, "device", prt['MFD'], "printer-id")
76 |
77 | print(f"MFD id for {prt['MFD']} is {mfd_id}")
78 |
79 | printQlist = list()
80 | for p in prt['printQueues']:
81 | split = p.split("\\",1)
82 | if not len(split) == 2:
83 | exit(f"Invalid data supplied {prt}")
84 |
85 | server_name = split[0].strip()
86 | printer_name = split[1].strip()
87 |
88 | print( f"Adding physical printer {server_name}/{printer_name}")
89 | printer_id = server_command.getPrinterProperty(auth, server_name, printer_name, "printer-id")
90 |
91 | printIDlist.append(printer_id) # Save this list of all printer ids for virtual q config
92 |
93 | printQlist.append({'server-name':server_name, 'printer-name':printer_name, 'printer-id':printer_id})
94 |
95 | record = {"q-list": printQlist, "mfd-name": prt['MFD'], "mfd-id": mfd_id}
96 | queue_list.append(record)
97 |
98 | print(queue_list)
99 |
100 | print( f"Setting Printer: {virtual_Q_printer} to hold print jobs\nApplying redirect queues {printIDlist} to {virtual_Q_printer}")
101 |
102 | server_command.setPrinterProperties(auth, virtual_Q_server, virtual_Q_printer,
103 | [["virtual", "True"],
104 | ["advanced-config.release-station", "STANDARD"],
105 | ["advanced-config.redirect.compatible-queues", ",".join(printIDlist)]])
106 |
107 | virtual_id = server_command.getPrinterProperty(auth, virtual_Q_server, virtual_Q_printer, "printer-id")
108 |
109 | for prt in queue_list:
110 |
111 | print( f"Checking Device: {prt['mfd-name']} is a Release Station")
112 |
113 | device_functions = server_command.getPrinterProperty(auth,"device", prt['mfd-name'], "device-functions")
114 |
115 | if not "RELEASE_STATION" in device_functions:
116 | print(f"Adding RELEASE_STATION to {prt['mfd-name']} functions")
117 | server_command.setPrinterProperty(auth, "device", prt['mfd-name'], "device-functions", device_functions + ",RELEASE_STATION")
118 |
119 | print( f"Setting redirect queues")
120 |
121 | qListID = ','.join([x['printer-id'] for x in prt['q-list']])
122 |
123 | server_command.setPrinterProperties(auth, "device", prt['mfd-name'],
124 | [["advanced-config.ext-device.assoc-printers", virtual_id ],
125 | ["advanced-config.ext-device.releases-on", qListID]])
126 |
127 | if len(prt["q-list"]) > 1: #TODO
128 | print(f"setting {prt['mfd-name']} to MULTIPLE_QUEUES")
129 | server_command.setPrinterProperty(auth, "device", prt['mfd-name'], "advanced-config.ext-device.find-me-release-mode", "MULTIPLE_QUEUES")
130 |
131 | print( "Restarting device")
132 | server_command.applyDeviceSettings(auth, prt['mfd-name'])
133 |
134 | #Done
135 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/PrinterManagement/ConfigureSecureFindMePrinting/printerList.csv:
--------------------------------------------------------------------------------
1 | epson-wf-c20590,l-alecc\EPSON Universal Print Driver (WF-C20590)
2 | epson-wf-c869r,l-alecc\EPSON WF-C869R
3 | epson-wf-r8590,l-alecc\epson-wf-r8590 (WF-R8590 Series)
4 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/PrinterManagement/configPrintersAndMFDsAfterFleetDeplyment/app.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import csv
4 | import xmlrpc.client
5 | from ssl import create_default_context, Purpose
6 | from sys import argv, exit
7 | import re
8 | import pprint
9 |
10 | pprint = pprint.pformat
11 |
12 |
13 | serverPrinterSplitter = re.compile(r'\\')
14 |
15 | host="https://localhost:9192/rpc/api/xmlrpc" # If not localhost then this address will need to be whitelisted in PaperCut
16 | auth="token" # Value defined in advanced config property "auth.webservices.auth-token". Should be random
17 |
18 | proxy = xmlrpc.client.ServerProxy(host, verbose=False,
19 | context = create_default_context(Purpose.CLIENT_AUTH))
20 |
21 | # with open('batch-devices-FX-New.csv', newline='') as csvfile:
22 |
23 | for file in argv[1:]:
24 | with open(file, newline='') as csvfile:
25 | deviceConfigReader = csv.DictReader(csvfile)
26 | for row in deviceConfigReader:
27 |
28 | if "deviceType" in row:
29 | # it's an MFD
30 |
31 | print("Found an MFD")
32 |
33 | server, printer = "device", row['deviceName']
34 |
35 | configData = (
36 | ("cost-model","SIZE_TABLE")
37 | ,("advanced-config.cost.pst.default.enabled", "Y")
38 | ,("advanced-config.cost.pst.default.color.duplex", f"{row['copy-cost:default.color.duplex']}")
39 | ,("advanced-config.cost.pst.default.color.simplex", f"{row['copy-cost:default.color.simplex']}")
40 | ,("advanced-config.cost.pst.default.grayscale.duplex", f"{row['copy-cost:default.grayscale.duplex']}")
41 | ,("advanced-config.cost.pst.default.grayscale.simplex", f"{row['copy-cost:default.grayscale.simplex']}")
42 | ,("advanced-config.cost.pst.A4.enabled", "Y")
43 | ,("advanced-config.cost.pst.A4.color.duplex", f"{row['copy-cost:default.color.duplex']}")
44 | ,("advanced-config.cost.pst.A4.color.simplex", f"{row['copy-cost:default.color.simplex']}")
45 | ,("advanced-config.cost.pst.A4.grayscale.duplex", f"{row['copy-cost:default.grayscale.duplex']}")
46 | ,("advanced-config.cost.pst.A4.grayscale.simplex", f"{row['copy-cost:default.grayscale.simplex']}")
47 | ,("advanced-config.cost.pst.A3.enabled", "Y")
48 | ,("advanced-config.cost.pst.A3.color.duplex", f"{row['copy-cost:A3.color.duplex']}")
49 | ,("advanced-config.cost.pst.A3.color.simplex", f"{row['copy-cost:A3.color.simplex']}")
50 | ,("advanced-config.cost.pst.A3.grayscale.duplex", f"{row['copy-cost:A3.grayscale.duplex']}")
51 | ,("advanced-config.cost.pst.A3.grayscale.simplex", f"{row['copy-cost:A3.grayscale.simplex']}")
52 | ,("advanced-config.ext-device.auth-mode.username-password", f"{row['auth-mode.username-password']}")
53 | ,("advanced-config.ext-device.auth-mode.card", f"{row['ext-device.auth-mode.card']}")
54 | ,("advanced-config.ext-device.self-association-with-card-enabled", f"{row['self-association-with-card-enabled']}")
55 | )
56 |
57 | else:
58 | # it's a printer
59 | print("Found a printer")
60 |
61 | server, printer = serverPrinterSplitter.split(row["printQueue"], maxsplit=2)
62 |
63 | configData = (
64 | ("cost-model","SIZE_TABLE") # Note hard coded
65 | ,("advanced-config.cost.pst.default.enabled", "Y")
66 | ,("advanced-config.cost.pst.default.color.duplex", f"{row['page-cost:default.color.duplex']}")
67 | ,("advanced-config.cost.pst.default.color.simplex", f"{row['page-cost:default.color.simplex']}")
68 | ,("advanced-config.cost.pst.default.grayscale.duplex", f"{row['page-cost:default.grayscale.duplex']}")
69 | ,("advanced-config.cost.pst.default.grayscale.simplex", f"{row['page-cost:default.grayscale.simplex']}")
70 | ,("advanced-config.cost.pst.A4.enabled", "Y")
71 | ,("advanced-config.cost.pst.A4.color.duplex", f"{row['page-cost:default.color.duplex']}")
72 | ,("advanced-config.cost.pst.A4.color.simplex", f"{row['page-cost:default.color.simplex']}")
73 | ,("advanced-config.cost.pst.A4.grayscale.duplex", f"{row['page-cost:default.grayscale.duplex']}")
74 | ,("advanced-config.cost.pst.A4.grayscale.simplex", f"{row['page-cost:default.grayscale.simplex']}")
75 | ,("advanced-config.cost.pst.A3.enabled", "Y")
76 | ,("advanced-config.cost.pst.A3.color.duplex", f"{row['page-cost:A3.color.duplex']}")
77 | ,("advanced-config.cost.pst.A3.color.simplex", f"{row['page-cost:A3.color.simplex']}")
78 | ,("advanced-config.cost.pst.A3.grayscale.duplex", f"{row['page-cost:A3.grayscale.duplex']}")
79 | ,("advanced-config.cost.pst.A3.grayscale.simplex", f"{row['page-cost:A3.grayscale.simplex']}")
80 | ,("advanced-config.watermark.enabled", f"{row['watermark.enabled']}")
81 | ,("advanced-config.watermark.text", f"{row['watermark.text']}")
82 | ,("advanced-config.watermark.font-size", f"{row['watermark.font-size']}")
83 | ,("advanced-config.watermark.gray-level", f"{row['watermark.gray-level']}")
84 | ,("advanced-config.watermark.position", f"{row['watermark.position']}")
85 | )
86 |
87 | # print(f"server = {server}, device = {printer}, config data = {pprint(configData)}")
88 | try:
89 | # Setup device/printer
90 | resp = proxy.api.setPrinterProperties(auth, server, printer, configData)
91 | if server == "device": # MFD needs a restart to get updates
92 | resp = proxy.api.applyDeviceSettings(auth, printer)
93 |
94 |
95 | except xmlrpc.client.Fault as error:
96 | print(f"\ncalled setPrinterProperties(). Return fault is {error.faultString}")
97 | exit(1)
98 | except xmlrpc.client.ProtocolError as error:
99 | print(f"\nA protocol error occurred\nURL: {error.url}\nHTTP/HTTPS headers: {error.headers}\n"
100 | f"Error code: {error.errcode}\nError message: {error.errmsg}")
101 | exit(1)
102 | except ConnectionRefusedError as error:
103 | print("Connection refused. Is the Papercut server running?")
104 | exit(1)
105 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/PrinterManagement/configPrintersAndMFDsAfterFleetDeplyment/batch-devices.csv:
--------------------------------------------------------------------------------
1 | auto,deviceType,deviceName,deviceHostnameOrIp,deviceAdminUser,deviceAdminPass,siteServerName,printQueues,findMeQueues,groups,location,copy-cost:default.color.duplex,copy-cost:default.color.simplex,copy-cost:default.grayscale.duplex,copy-cost:default.grayscale.simplex,copy-cost:A3.color.duplex,copy-cost:A3.color.simplex,copy-cost:A3.grayscale.duplex,copy-cost:A3.grayscale.simplex,auth-mode.username-password,ext-device.auth-mode.card,self-association-with-card-enabled
2 | N,generic,dev-test-device,localhost,,,,,l-alecc\PaperCut Global PostScript,,Office,0.03,0.05,0.02,0.02,0.03,0.05,0.05,0.02,N,Y,Y
3 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/PrinterManagement/configPrintersAndMFDsAfterFleetDeplyment/batch-printers.csv:
--------------------------------------------------------------------------------
1 | printQueue,page-cost:default.color.duplex,page-cost:default.color.simplex,page-cost:default.grayscale.duplex,page-cost:default.grayscale.simplex,page-cost:A3.color.duplex,page-cost:A3.color.simplex,page-cost:A3.grayscale.duplex,page-cost:A3.grayscale.simplex,watermark.enabled,watermark.text,watermark.font-size,watermark.gray-level,watermark.position
2 | l-alecc\PaperCut Global PostScript,0.03,0.05,0.02,0.02,0.03,0.05,0.05,0.02,Y,"Printed by %user% at %date% on printer %printer%",5,LIGHT,BOTTOM
3 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/PrinterManagement/setAllPrinterAuthMode.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | # NOTE: Needs PaperCut MF 19.1 or above
5 |
6 |
7 | import xmlrpc.client
8 | from ssl import create_default_context, Purpose
9 | import sys
10 |
11 | host = "https://localhost:9192/"
12 |
13 | webServicesURL = host + "rpc/api/xmlrpc"
14 |
15 |
16 | # Value defined in advanced config property "auth.webservices.auth-token". Should be random
17 | auth = "token"
18 |
19 | proxy = xmlrpc.client.ServerProxy( webServicesURL, verbose=False,
20 | context = create_default_context(Purpose.CLIENT_AUTH))
21 |
22 |
23 |
24 | settings = [
25 | ["advanced-config.ext-device.auth-mode.username-password", "Y"],
26 | ["advanced-config.ext-device.auth-mode.card", "Y"],
27 | ["advanced-config.ext-device.auth.pin-required-for-card", "N"],
28 | ["advanced-config.ext-device.self-association-with-card-enabled", "Y"],
29 | ["advanced-config.ext-device.self-association-allowed-card-regex", "^\d{4}$"], # Card numbers must be exactly 4 decimal digits
30 | ["advanced-config.ext-device.auth-mode.identity-no", "N"],
31 | ]
32 |
33 | if __name__ == "__main__":
34 | # execute only if run as a script
35 |
36 | offset = 0
37 |
38 | limit = 100 # Max number of printers to retrieve on each call
39 |
40 | while True:
41 | try:
42 | printerList = proxy.api.listPrinters(auth, offset,limit)
43 | except xmlrpc.client.Fault as error:
44 | print("\ncalled listPrinters(). Return fault is {}".format(error.faultString))
45 | sys.exit(1)
46 | except xmlrpc.client.ProtocolError as error:
47 | print("\nA protocol error occurred\nURL: {}\nHTTP/HTTPS headers: {}\nError code: {}\nError message: {}".format(
48 | error.url, error.headers, error.errcode, error.errmsg))
49 | sys.exit(1)
50 |
51 | for sp in printerList:
52 |
53 | splitID = sp.split("\\")
54 |
55 | if splitID[0] != "device":
56 | continue # It's a printer, don't bother
57 |
58 | deviceName = splitID[1]
59 |
60 | print(f"Configuring device {deviceName}, result is {'success' if proxy.api.setPrinterProperties(auth, 'device', deviceName, settings) else 'failure'}")
61 | print(f"Applying new settings to device {deviceName}, result is {'success' if proxy.api.applyDeviceSettings(auth, deviceName) else 'failure'}")
62 |
63 | if len(printerList) < limit:
64 | break # We have reached the end
65 |
66 | offset += limit # We need to next slice of printers
67 |
68 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/README.md:
--------------------------------------------------------------------------------
1 | # Various scripts and programs that use server-command or web services API.
2 |
3 | For more information about using `server-command` or the web services API see our
4 | knowledge base [article](https://www.papercut.com/kb/Main/TopTipsForUsingThePublicWebServicesAPI)
5 | and our [wiki](https://github.com/PaperCutSoftware/PaperCutExamples/wiki).
6 |
7 | * `addInfoToCSVreport.py`: Python script to add extra information a shared account report as a post process
8 | * `assignPrintersToServerGroups.sh`: Bash script to assign printers to specific group depending on the print server they are attached to
9 | * `allUserInDelegationGroup.sh`: Bash script to add all users into a single internal group for delegation purposes
10 | * `csvListOfPrintersAndFieldValue.sh`: Bash script to print csv list of all printers and the value of a specific property. Defaults to the `disabled` property
11 | * `delete-users-matching-regex`: Small bash script to delete any user whose username matches a regular expression
12 | * `delete-users-matching-regex.ps1`: Small Windows Powershell script to delete any user whose username matches a regular expression
13 | * `displaySharedBalanceWhenAutoChargeToSharedAccount.py`: Lists all users
14 | who are automatically charging to a shared account and the shared account balance (Python)
15 | * `findAllUsersInDept.py`: Finds all users in a specific department (Python)
16 | * `findAllUsersInGroup.py`: Finds all users in a specific user group (Python)
17 | * `list-shared-account-balances`: Bash script to list all shared accounts and the balance
18 | * `list-shared-account-balances.ps1`: Windows Powershell script to list all shared accounts and the balance
19 | * `list-user-with-no-card-no-1.ps1`: Windows Powershell script to list all users with no primary card number assigned
20 | * `listGroupMembership.sh`: For every user account, list group membership (Bash script)
21 | * `listPrinterStatuses.py`: Python program to show the use of `printer-id` property via the
22 | web services API to streamline using the health interface API
23 | * `loadSharedAcconts.py`: Python program that generates a large file of shared accounts and loads into PaperCut.
24 | Shows how to use the `getTaskStatus()` method
25 | * `removeTempCardsJob.py`: Removes all temp cards from PaperCut, if the cards are listed in a table.
26 | Easy to extend to use an external database (Python)
27 | * `removeUsersListedInFile`: Bash script that removes any user _NOT_ listed in a file
28 | * `setAccountSelectionMode.py`: Python script to set account selection mode depending on number shared accounts
29 | user can access
30 | * `simpleRefundBalance`: Small and ugly web application to allow users to request a refund of thier PaperCut balance
31 | * `simpleTopUpBalance`: Small and ugly web application to allow users to top up their PaperCut personal account balance
32 | * `swapCardNumbers.sh`: Bash script to swap the primary-card-number and secondary-card-number fields for all users
33 | * `swapCardNumbers.ps1`: Powershell script to swap the primary-card-number and secondary-card-number fields for all users.
34 | Note: this script shows how to use the ServerCommandProxy DLL, instead of calling the `server-command` utility
35 | * `topUpOverDrawnAccounts.sh`: Bash script to adjust all -ve balances, presumbly with external funds
36 | * `Update_Card_Number`: Perl script to read usernames and card numbers from csv file and update PaperCut user details with the card number
37 | * `userSelectSharedAccount`: Small and ugly web application to allow users to change their own default shared account
38 | * `../Reports/listArchiveDir.sh`: SQL script to list the archives for a specific printer that have happended since a given date.
39 | Find it [here](https://github.com/PaperCutSoftware/PaperCutExamples/blob/master/Reports/listArchiveDir.sh)
40 | * `setUserPropeties.php`: A trival PHP 7 example that shows how to handle encoding more complex paramters
41 | * `C/xmlrpc.c`: The basics of calling PaperCut web services from C. Inclues a Makefile to help you build the example code
42 |
43 | Note:
44 | * If you are using the Perl scripting language to interface with PaperCut APIs
45 | then use the Data::Dumper module to help understand the data
46 | you get from PaperCut (and how your language library is presenting the information).
47 |
48 | More information on this module at http://perldoc.perl.org/Data/Dumper.html
49 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/ServerCommandProxy-dll-setup.ps1:
--------------------------------------------------------------------------------
1 | # Create the PaperCut MF/NG server-command proxy DLL
2 |
3 | # See swapCardNumbers.ps1 for example use
4 | # Assumes you have a local install of PaperCut MF
5 |
6 |
7 | dotnet new classlib --name ServerCommandProxy
8 | cd ServerCommandProxy
9 | Remove-Item Class1.cs
10 | Copy-Item 'C:\Program Files\PaperCut MF\server\examples\webservices\csharp\ServerCommandProxy.cs'
11 | dotnet add package Kveer.XmlRPC --version 1.1.1
12 | dotnet build --configuration Release
13 | cd ..
14 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/Update_Card_Number/sample.csv:
--------------------------------------------------------------------------------
1 | user01,123456
2 | user02,987655
3 | user03,foobar
4 | alec,1234
5 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/Update_Card_Number/update_card_numbers.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 | #
3 | ## Update Card Numbers from CSV
4 | # Uses RPC::XML - http://search.cpan.org/~rjray/RPC-XML-0.79/lib/RPC/XML.pm
5 | # to support https connections also install LWP::Protocol::https
6 |
7 | # Usage: resp_number.pl sample.csv, or reads account details from stdin
8 |
9 | use RPC::XML;
10 | use RPC::XML::Client;
11 |
12 | use strict;
13 |
14 | my $server = "172.31.240.1"; my $port="9192";
15 | my $token = "password";
16 | my $url = "https://${server}:${port}/rpc/api/xmlrpc";
17 |
18 | my $client = RPC::XML::Client->new($url);
19 |
20 | my $updated;
21 |
22 | while (<>){
23 | chomp;
24 | my ($user, $cardno) = split ",";
25 |
26 | my $resp = $client->send_request('api.setUserProperty', $token, $user,
27 | 'card-number', RPC::XML::string->new($cardno));
28 |
29 | die "$resp\n" unless ref($resp);
30 |
31 | if ($resp->value == 1){
32 | $updated++
33 | } else {
34 | print STDERR "Failed to update $user\n";
35 | }
36 |
37 | }
38 | print "\nFinshed: $updated users have been updated\n";
39 |
40 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/addInfoToCSVreport.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from csv import reader
4 | from sys import stdin
5 |
6 | from xmlrpc.client import ServerProxy
7 | from ssl import create_default_context, Purpose
8 |
9 |
10 | # Script to user account notes to the Shared account configuration report(account_configurations.csv)
11 |
12 | host="https://localhost:9192/rpc/api/xmlrpc" # If not localhost then this address will need to be whitelisted in PaperCut
13 |
14 | auth="token" # Value defined in advanced config property "auth.webservices.auth-token". Should be random
15 |
16 | proxy = ServerProxy(host, verbose=False,
17 | context = create_default_context(Purpose.CLIENT_AUTH))#Create new ServerProxy Instance
18 |
19 | # #TODO open and manipulate CSV
20 | csv_reader = reader(stdin, delimiter=',') #Read in standard input stream
21 | line_count = 0
22 | for row in csv_reader:
23 |
24 | if line_count == 1: #Header row
25 | row.insert(4,"Notes data")
26 | elif line_count > 2:
27 | row.insert(4,proxy.api.getSharedAccountProperty(auth, row[0] + "\\" + row[2], "notes")) #Add Note data for shared account(Parent or child)
28 | print(", ".join(row))
29 | line_count += 1
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/allUserInDelegationGroup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # If a user is to be allowed to be a delegate for all users in an organisation
4 | # it would be useful to add the "[All Users]" group to the list. However you
5 | # can't do this as it's not a real group.
6 |
7 | # There is a workaround. Create a real group that has all PaperCut users in it.
8 | # If you can't do this in your directory source then this can be automatically
9 | # done using a script (like this one) run every couple of hours for instance
10 | # on the PaperCut server.
11 |
12 | # The script needs write permission to the appropriate location)
13 |
14 | # Note that his example will overwrite any current contents.
15 | # If you want to preserver existing information in the file then
16 | # you will need something more sophisticated.
17 |
18 | # See also https://www.papercut.com/kb/Main/InternalGroups
19 |
20 | server-command list-user-accounts | xargs -I {} echo "AllUser:{}" > ~papercut/server/data/conf/additional-groups.txt
21 |
22 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/assignPrintersToServerGroups.ps1:
--------------------------------------------------------------------------------
1 | # Assign each printer to a group that corresponds to the print server name
2 |
3 | # Developed by Matt Grant
4 |
5 | #Declare $result as array
6 |
7 | $result = @()
8 |
9 | #Iterator for loop
10 |
11 | $n = 0
12 |
13 | #Query to retrieve list of printers. Drops the template printer and devices. Replaces \ with space
14 |
15 | $parameters = 'list-printers | Select-String -Pattern "^((?!!!|device).)*$" | ForEach-Object {$_ -Replace "\\"," "}'
16 |
17 | #Run the command and put it in $result
18 |
19 |
20 | # $result = Invoke-Expression "& '/Applications/PaperCut MF/server/bin/mac/server-command' $parameters"
21 |
22 | $result = Invoke-Expression "& 'C:\Program Files\PaperCut MF\server\bin\win\server-command.exe' $parameters"
23 |
24 |
25 |
26 | #iterate through each line in the array
27 |
28 | foreach ($line in $result) {
29 |
30 | #splits the output so that the first word is put into $server and the consequent string is put into $queue
31 |
32 | $server,$queue = ($result[$n] -split ' ',2)
33 |
34 | #Removes -ps from the servername, this is only required for my customer as all the server names are like c30-ps.
35 |
36 | #Comment out or change as required
37 |
38 | $printergroup =($server -split '-')[0]
39 |
40 | #Handy for debugging
41 |
42 | $Output = "Adding printer group: $printergroup to Queue: $queue on server: $server"
43 |
44 | $Output
45 |
46 | #Run the command that adds the printer group to the printer
47 |
48 | # Invoke-Expression "& '/Applications/PaperCut MF/server/bin/mac/server-command' add-printer-group $server '$queue' $printergroup"
49 |
50 | Invoke-Expression "& 'C:\Program Files\PaperCut MF\server\bin\win\server-command.exe' add-printer-group $server '$queue' $printergroup"
51 |
52 | #iterate through the array
53 |
54 | $n = $n + 1
55 |
56 | }
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/assignPrintersToServerGroups.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Add each printer to a specific group based on the server attached to
4 |
5 | # if you are using Windows then you can create a Powershell equivalent..
6 |
7 | # Note if printers are moved the group membership can be updated via the user interface
8 |
9 |
10 | # A simple PaperCut server script can do this. The process is as follows.
11 |
12 | # 1) list all printers
13 | # 2) Remove the template printer and all the devices from the lsit
14 | # 3) Replace the '\' with a space
15 | # 4) For each server printer run the add-printer-group command
16 |
17 | # e.g. Using Linux or MacOS
18 |
19 | server-command list-printers | egrep -v '^(!!)|(device)' | tr "\\" " " | while read server printer ; do
20 | server-command add-printer-group $server $printer "Server:$server"
21 | done
22 |
23 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/automaticallyChargeToSingleSharedAccount/automaticallyChargeToSingleSharedAccount.ps1:
--------------------------------------------------------------------------------
1 | # Navigate to the location of the server-command executable.
2 | cd "C:\Program Files\PaperCut MF\server\bin\win"
3 |
4 | # Set the location of the CSV file contain the user to shared account mappings.
5 | $data = Import-CSV -Path C:\users\installer\desktop\example-csv.csv -Delimiter ";"
6 |
7 | # We will run through each record in the CSV and set the user to 'Automatically charge to a single shared account' and assign the relevant account.
8 | $data | ForEach {
9 |
10 | $user = $_.username
11 | $account = $_.account
12 |
13 | # Set Shared shared account for user
14 | Invoke-Expression "& 'C:\Program Files\PaperCut MF\server\bin\win\server-command.exe' set-user-account-selection-auto-select-shared-account $user $account FALSE"
15 | }
16 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/automaticallyChargeToSingleSharedAccount/example-csv.csv:
--------------------------------------------------------------------------------
1 | username;account
2 | ann;Biology
3 | betty;Chemistry
4 | donna;Drama
5 | harold;English
6 | jane;French
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/automaticallyChargeToSingleSharedAccount/readme.md:
--------------------------------------------------------------------------------
1 | In PaperCut NG or MF, you have the option to charge all of the activity performed by a specific user to a single shared account. This option can be found under **Users** > **Select a user** > **Account Selection**.
2 | Once this option is selected, a specific shared account can be specified for user activity to be charged against.
3 |
4 | This PowerShell example uses our server-command utility to set this setting for specific users as specified in an accompanying CSV file.
5 | For more information on server-command, see https://www.papercut.com/help/manuals/ng-mf/common/tools-server-command/
6 |
7 | The PowerShell script should be adjusted on line 6 to specify the location of your input CSV file.
8 |
9 | The CSV file should be in the following format:
10 | | username | account |
11 | | -------- | --------- |
12 | | ann | Biology |
13 | | betty | Chemistry |
14 | | donna | Drama |
15 | | harold | English |
16 | | jane | French |
17 |
18 | This script is designed to be executed on the PaperCut NG or MF Application Server.
19 |
20 | Please note that you may need to adjust or bypass the script Execution Policy on the server before any PowerShell scripts can be executed successfully.
21 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/changePrinterChargingType.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Find all the users in a specific user group
4 |
5 | import xmlrpc.client
6 | from ssl import create_default_context, Purpose
7 | import sys
8 |
9 | host="https://localhost:9192/rpc/api/xmlrpc" # If not localhost then the client address will need to be whitelisted in PaperCut
10 |
11 | auth="secret" # Value defined in advanced config property "auth.webservices.auth-token". Should be random
12 |
13 | #proxy = xmlrpc.client.ServerProxy(host, verbose=True, transport=xmlrpc.client.SafeTransport, context=ctx)
14 | proxy = xmlrpc.client.ServerProxy(host, verbose=False,
15 | context = create_default_context(Purpose.CLIENT_AUTH))
16 |
17 | #if len(sys.argv) != 2:
18 | # print("No group name provided")
19 | # sys.exit(1)
20 |
21 | #group = sys.argv[1] # The user group of interest
22 |
23 | serverName = "dipto-Latitude-7490"
24 | printerName = "PDF"
25 | propertyName = "cost-model"
26 | propertyValue = "SIMPLE"
27 |
28 | try:
29 | command_feedback = proxy.api.setPrinterProperty(auth, serverName, printerName, propertyName, propertyValue)
30 | # print("Got user list {}".format(userList))
31 | except xmlrpc.client.Fault as error:
32 | print("\ncalled listUserAccounts(). Return fault is {}".format(error.faultString))
33 | sys.exit(1)
34 | except xmlrpc.client.ProtocolError as error:
35 | print("\nA protocol error occurred\nURL: {}\nHTTP/HTTPS headers: {}\nError code: {}\nError message: {}".format(
36 | error.url, error.headers, error.errcode, error.errmsg))
37 | sys.exit(1)
38 |
39 |
40 | print(command_feedback)
41 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/csvListOfPrintersAndPropertyValue.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # print a csv list of all the printers with the value of the printer property in $1.
4 |
5 | # If $1 is not given defaults to the "disabled" propery
6 |
7 | # For a list of possible properties see
8 | # https://github.com/PaperCutSoftware/PaperCutExamples/wiki/Get-Set-Advanced-Printer-Properties
9 |
10 |
11 | server-command list-printers |
12 | while IFS='\' read -r s p ; do
13 | if [[ $s != '!!template printer!!' && $s != "device" ]] ; then
14 | echo \""$s\",\"$p\",\"$(server-command get-printer-property $s $p ${1:-disabled})\""
15 | fi
16 | done
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/delete-printers-matching-regex.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Deletes all printers in PaperCut that match a specific regular expression
4 | # Use with care!
5 | # Works on Mac and Linux (or Cygwin on Windows)
6 |
7 | REGEX=${1:-'..*'} # Provide a default that matches all printer names
8 |
9 | if [[ $(( $(server-command list-printers | grep "$REGEX" | wc -l ) )) == 0 ]] ; then
10 | echo "No printers match \"$1\"."
11 | else
12 | server-command list-printers | grep "$REGEX"
13 |
14 | echo
15 | read -p "About to delete this list of printers. Are you sure? [N/y] " -r < /dev/tty
16 |
17 | if [[ $REPLY =~ ^[Yy]$ ]] ; then
18 | server-command list-printers | grep mcx | while read -r x ; do server-command delete-printer ${x%%\\*} ${x##*\\} ; done
19 | fi
20 | fi
21 |
22 |
23 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/delete-users-matching-regex:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Deletes all user accounts in PaperCut that match a specific regular expression
4 | # Use with care!
5 | # Works on Mac and Linux (or Cygwin on Windows)
6 |
7 | REGEX=${1:-'..*'} # Provide a default that matches all account names
8 |
9 | if [[ $(( $(server-command list-user-accounts | grep "$REGEX" | wc -l ) )) == 0 ]] ; then
10 | echo "No user accounts match \"$1\"."
11 | else
12 | server-command list-user-accounts | grep "$REGEX"
13 |
14 | echo
15 | read -p "About to delete this list of users. Are you sure? [N/y] " -r < /dev/tty
16 |
17 | if [[ $REPLY =~ ^[Yy]$ ]] ; then
18 | server-command list-user-accounts | grep "$REGEX" | xargs -n 1 server-command delete-existing-user
19 | fi
20 | fi
21 |
22 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/delete-users-matching-regex.ps1:
--------------------------------------------------------------------------------
1 | # Deletes all user accounts in PaperCut that match a specific regular expression
2 | # Use with care!
3 |
4 | # Provide a default that matches all account names
5 | param([String]$regex='.+')
6 |
7 | $SERVER_COMMAND = 'C:\Program Files\PaperCut MF\server\bin\win\server-command.exe'
8 |
9 | & $SERVER_COMMAND list-user-accounts | Where-Object {$_ -cmatch $regex} | Out-Host -Paging
10 |
11 | if (Read-Host -Prompt 'About to delete this list of users. Are you sure? [N/y] ' | Where-Object {$_ -cmatch 'y' } ) {
12 | & $SERVER_COMMAND list-user-accounts | Where-Object {$_ -cmatch $regex} | ForEach-Object -Process {& $SERVER_COMMAND delete-existing-user $_ }
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/deleteUsersAfterPeriod.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # For Mac or Linux servers running a Postgrsql database
4 | # Modify to suite
5 |
6 | # Deletes user accounts after a fixed period if the user name matches a mask
7 |
8 | # $1 Period
9 | # $2 Name Mask optional
10 |
11 | # For example
12 | # $ ./deleteUsersAfterPeriod.sh '1 Year' 'Guest-*'
13 |
14 | # Notes:
15 | # * to discover the correct path on a Windows system
16 | # take the value from tbl_printer_usage_log.archive_path & replace '/' with '\'
17 | # * you must be running an external database to run SQL queries.
18 | # https://www.papercut.com/kb/Main/RunningPaperCutOnAnExternalDatabase
19 |
20 | DATABASE=papercutdb
21 | DBUSER=papercut
22 |
23 | p=${1:?'Must provide a period length, e.g. "1 Year"'}
24 | m=${2:-"%"}
25 |
26 | psql -Abatqd $DATABASE -U $DBUSER --set=period=$p --set=userMask=$m < today:
92 | print (f"{user} will expire on {expirtyDate}")
93 | else:
94 | print (f"{user} has expired {expirtyDate}")
95 |
96 | #HERE you could add user to delete list, or perform other action
97 |
98 | if limit == 0 or len(user_list) < limit:
99 | break # We have reached the end
100 |
101 | offset += limit # We need to next slice of users
102 |
103 | if counter == 0:
104 | print(f"\nThere are no expiring users")
105 | elif counter>1:
106 | print(f"\nThere are {counter} expiring users")
107 | else:
108 | print(f"\nThere is one expiring user")
109 |
110 | if __name__=="__main__":
111 |
112 | if len(argv) == 1: #no argument, expired today and in the past
113 | list_users(0)
114 | elif len(argv) == 2:
115 | try:
116 | offset_days = int(argv[1])
117 | list_users(offset_days)
118 | except ValueError:
119 | print("Usage: ./listExpiredUsers.py [how_soon] or leave it blank to return all past record(s)")
120 | else:
121 | print("Usage: ./listExpiredUsers.py [how_soon] or leave it blank to return all past record(s)")
122 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/listGroupMembership.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 |
4 | server-command list-user-accounts | xargs -I {} echo 'printf "%s:\t%s\n" {} : $(server-command get-user-groups {})'|bash
5 |
6 |
7 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/listPrinterStatuses.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import xmlrpc.client
4 | from ssl import create_default_context, Purpose
5 | import sys
6 | import requests
7 |
8 | # To use tls see http://docs.python-requests.org/en/master/user/advanced/
9 | host = "http://localhost:9191/"
10 |
11 |
12 | healthURL = host + "api/health/{}/{}/status"
13 | webServicesURL = host + "rpc/api/xmlrpc"
14 |
15 | # See https://www.papercut.com/support/resources/manuals/ng-mf/common/topics/tools-monitor-system-health-api-authorization-key.html
16 | healthAuth = {'Authorization':"XrACcfMNEtfxnEpXeawi52mRneEwkXYd"}
17 |
18 | # Value defined in advanced config property "auth.webservices.auth-token". Should be random
19 | auth = "token"
20 |
21 | proxy = xmlrpc.client.ServerProxy( webServicesURL, verbose=False,
22 | context = create_default_context(Purpose.CLIENT_AUTH))
23 |
24 | def getDevicePrinterID(server, printer):
25 |
26 | try:
27 | return proxy.api.getPrinterProperty(auth, server, printer, "printer-id" )
28 | except xmlrpc.client.Fault as error:
29 | # If the printer is not found this exception will get thrown
30 | print("\ncalled getPrinterProperty(). Return fault is {}".format(error.faultString))
31 | sys.exit(1)
32 | except xmlrpc.client.ProtocolError as error:
33 | print("\nA protocol error occurred\nURL: {}\nHTTP/HTTPS headers: {}\nError code: {}\nError message: {}".format(
34 | error.url, error.headers, error.errcode, error.errmsg))
35 | sys.exit(1)
36 |
37 | def getDevicePrinterStatus(server, printer):
38 |
39 | printerID = getDevicePrinterID(server, printer)
40 |
41 | if server == "device":
42 | type="devices"
43 | else:
44 | type="printers"
45 |
46 | try:
47 | printerStatusResponse = requests.get( healthURL.format(type, printerID), headers=healthAuth)
48 | except ConnectionError as error:
49 | print("Network problem calling health API: {}".format(error))
50 | sys.exit(1)
51 | except requests.exceptions.Timeout as error:
52 | print("Network call to health API timed out: {}".format(error))
53 | sys.exit(1)
54 | except requests.exceptions.RequestException as error:
55 | print("Network error on health API failed: {}".format(error))
56 | sys.exit(1)
57 |
58 | try:
59 | jsonResponse = printerStatusResponse.json()
60 | except ValueError as error:
61 | print("Could decode json from health API: {}".format(error))
62 | sys.exit(1)
63 |
64 | return printerStatusResponse.status_code, jsonResponse["status"]
65 |
66 |
67 | if __name__ == "__main__":
68 | # execute only if run as a script
69 |
70 | offset = 0
71 |
72 | limit = 100 # Max number of printers to retrieve on each call
73 |
74 | while True:
75 | try:
76 | printerList = proxy.api.listPrinters(auth, offset,limit)
77 | except xmlrpc.client.Fault as error:
78 | print("\ncalled listPrinters(). Return fault is {}".format(error.faultString))
79 | sys.exit(1)
80 | except xmlrpc.client.ProtocolError as error:
81 | print("\nA protocol error occurred\nURL: {}\nHTTP/HTTPS headers: {}\nError code: {}\nError message: {}".format(
82 | error.url, error.headers, error.errcode, error.errmsg))
83 | sys.exit(1)
84 |
85 | for sp in printerList:
86 | if sp == "!!template printer!!":
87 | continue
88 | splitID = sp.split("\\")
89 | server= splitID[0]
90 | printer = splitID[1]
91 |
92 | status, text = getDevicePrinterStatus(server, printer)
93 |
94 | if server == "device":
95 | type="device"
96 | else:
97 | type="printer"
98 |
99 | if status != 200:
100 | print("Status for {} {}/{} is {} -- DEVICE/PRINTER IN ERROR".format(
101 | type,
102 | server,
103 | printer,
104 | text))
105 | else:
106 | print("Status for {} {}/{} is {}".format(
107 | type,
108 | server,
109 | printer,
110 | text))
111 |
112 | if len(printerList) < limit:
113 | break # We have reached the end
114 |
115 | offset += limit # We need to next slice of printers
116 |
117 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/loadSharedAccounts.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Load a lot of data and use getTaskStatus()
4 |
5 | import xmlrpc.client
6 | from ssl import create_default_context, Purpose
7 | from time import sleep
8 | from sys import exit
9 |
10 | host="https://localhost:9192/rpc/api/xmlrpc" # If not localhost then this address will need to be whitelisted in PaperCut
11 |
12 | auth="password" # Value defined in advanced config property "auth.webservices.auth-token". Should be random
13 | # Generate a file of data
14 |
15 |
16 | print("Creating Data Import file")
17 |
18 | fileName = "/tmp/import.file"
19 |
20 | f = open(fileName, 'w')
21 |
22 | for p in range(20):
23 | f.write("parentAccount{0:03}\t\tY\tPPx{0:03}\t10.0\tN\t[All Users]\t\t\t\t\n".format(p))
24 | for a in range(500):
25 | f.write("{0:03}LongParentAccountname\t{1:03}ReallyLongSharedAccountName\t\t{0:03}x{1:03}\t10.0\t\t\t[All Users]\t\t\t\n".format(p, a))
26 |
27 | f.close()
28 | print("Data Import file created")
29 |
30 | proxy = xmlrpc.client.ServerProxy(host, verbose=False,
31 | context = create_default_context(Purpose.CLIENT_AUTH))
32 |
33 | print("Starting Data File Upload")
34 |
35 | try:
36 | proxy.api.batchImportSharedAccounts(auth, fileName, False, False)
37 | except xmlrpc.client.Fault as error:
38 | print("\ncalled batchImportSharedAccounts(). Return fault is {}".format(error.faultString))
39 | exit(1)
40 | except xmlrpc.client.ProtocolError as error:
41 | print("\nA protocol error occurred\nURL: {}\nHTTP/HTTPS headers: {}\nError code: {}\nError message: {}".format(
42 | error.url, error.headers, error.errcode, error.errmsg))
43 | exit(1)
44 |
45 | status = proxy.api.getTaskStatus()
46 | lineCount = 0
47 | while not status["completed"]:
48 | sleep(3) # Wait three seconds so server is not overloaded
49 | print(".",end="", flush=True) # Show the user something is happing
50 | print("\nStatus: {}\t Message: {}".format(status['completed'],status['message'].splitlines()[lineCount]))
51 | status = proxy.api.getTaskStatus()
52 | lineCount += 1
53 |
54 | # Only want the last line of messages
55 | last=status["message"].splitlines()[-1]
56 |
57 | print("\n{}".format(last))
58 |
59 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/lookUpUserByProperty/README.md:
--------------------------------------------------------------------------------
1 | # Locate a user on something other than the username
2 |
3 | When using server command users are identified by their username. This is guaranteed to be unique.
4 |
5 | But sometimes it would be useful find user accounts based on other attributes, such as full name or card number
6 |
7 | The card number is easy. Use `look-up-user-name-by-card-no` sub command of `server-command`. However things like the
8 | user's full name are a little harder.
9 |
10 | The following approach will be slow but does work. It uses the features of the Bash shell, but the same
11 | approach is possible with the Windows PowerShell as well.
12 |
13 | The script `getPPCuserName.sh` will take the name of attribute and a value. It will then return of username
14 | of the account that matches. However be careful, as it could be zero accounts or more than one. It will be slow as it
15 | looks at each user account in turn until it finds a match.
16 |
17 | For example
18 |
19 | `server-command adjust-user-account-balance $(./getPPCuserName.sh full-name "Alec Clews") 30 'Add $30 to user Alec Clews' default`
20 |
21 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/lookUpUserByProperty/getPPCuserName.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | server-command list-user-accounts | while read -r u ; do
4 | if [[ $(server-command get-user-property $u ${1:?'Must supply user property name'}) == "${2:?'Must supply value to match'}" ]] ; then
5 | echo $u
6 | # Might want to exit here on 1st match
7 | fi
8 | done
9 |
10 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/processExpiredUserAccoounts.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env python3
2 |
3 | from xmlrpc import client as xmlrpcclient
4 |
5 | from ssl import create_default_context, Purpose
6 | from time import sleep
7 | from sys import exit
8 | from json import dumps, loads
9 | from datetime import date, timedelta
10 |
11 | host = "https://localhost:9192/rpc/api/xmlrpc" # If not localhost then this address will need to be whitelisted in PaperCut
12 |
13 | auth = "token" # Value defined in advanced config property "auth.webservices.auth-token". Should be random
14 |
15 | accountExpiry = 10 # No of days before an new account is deleted.
16 |
17 | proxy = xmlrpcclient.ServerProxy(host, verbose=False,
18 | context = create_default_context(Purpose.CLIENT_AUTH))
19 |
20 |
21 | offset = 0
22 |
23 | limit = 100 # Max number of usernames to retrieve on each call
24 |
25 | while True:
26 | try:
27 | userList = proxy.api.getGroupMembers(auth, "!!Internal Users!!", offset,limit)
28 | # print("Got user list {}".format(userList))
29 | except xmlrpcclient.Fault as error:
30 | print("\ncalled getGroupMembers(). Return fault is {}".format(error.faultString))
31 | exit(1)
32 | except xmlrpcclient.ProtocolError as error:
33 | print("\nA protocol error occurred\nURL: {}\nHTTP/HTTPS headers: {}\nError code: {}\nError message: {}".format(
34 | error.url, error.headers, error.errcode, error.errmsg))
35 | exit(1)
36 |
37 | for user in userList:
38 | try:
39 | notes = proxy.api.getUserProperty(auth, user, "notes") # Get a list of groups for the current user of interest
40 | # print("Got group list {} for user {}".format(groups, user))
41 | except xmlrpcclient.Fault as error:
42 | print("\ncalled getUserProperty(). Return fault is {}".format(error.faultString))
43 | exit(1)
44 | except xmlrpcclient.ProtocolError as error:
45 | print("\nA protocol error occurred\nURL: {}\nHTTP/HTTPS headers: {}\nError code: {}\nError message: {}".format(
46 | error.url, error.headers, error.errcode, error.errmsg))
47 | exit(1)
48 |
49 | try:
50 | userInfo = loads(notes)
51 | except:
52 | userInfo = {}
53 |
54 | if not "expiry-date" in userInfo:
55 | userInfo['expiry-date'] = (date.today() + timedelta(days=accountExpiry)).isoformat()
56 | print(userInfo['expiry-date'])
57 | try:
58 | proxy.api.setUserProperty(auth, user, "notes", dumps(userInfo))
59 | except xmlrpcclient.Fault as error:
60 | print("\ncalled setUserProperty(). Return fault is {}".format(error.faultString))
61 | exit(1)
62 | except xmlrpcclient.ProtocolError as error:
63 | print("\nA protocol error occurred\nURL: {}\nHTTP/HTTPS headers: {}\nError code: {}\nError message: {}".format(
64 | error.url, error.headers, error.errcode, error.errmsg))
65 | exit(1)
66 |
67 | if date.fromisoformat(userInfo['expiry-date']) < date.today():
68 | try:
69 | proxy.api.deleteExistingUser(auth, user, True)
70 | except xmlrpc.client.Fault as error:
71 | print("\ncalled deleteExistingUser(). Return fault is {}".format(error.faultString))
72 | exit(1)
73 | except xmlrpc.client.ProtocolError as error:
74 | print("\nA protocol error occurred\nURL: {}\nHTTP/HTTPS headers: {}\nError code: {}\nError message: {}".format(
75 | error.url, error.headers, error.errcode, error.errmsg))
76 | exit(1)
77 |
78 | print(f"Deleted user {user}")
79 |
80 | if len(userList) < limit:
81 | break # We have reached the end
82 |
83 | offset += limit # We need to next slice of users
84 |
85 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/removeTempCardsJob.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # If a user has staretd using a temp card this job will remove the card from the card field
4 |
5 | import xmlrpc.client
6 | import sys
7 |
8 | host="http://localhost:9191/rpc/api/xmlrpc" # If not localhost then this address will need to be whitelisted in PaperCut
9 |
10 | auth="atoken" # Value defined in advanced config property "auth.webservices.auth-token". Should be random
11 |
12 | proxy = xmlrpc.client.ServerProxy(host)
13 |
14 | cardDatabase = [ # List of the tempcards
15 | "1234",
16 | "2345",
17 | "3456",
18 | ]
19 |
20 | for card in cardDatabase:
21 | username = proxy.api.lookUpUserNameByCardNo(auth, card)
22 | print("Looking up card {}".format(card))
23 | if len(username) > 0:
24 | if proxy.api.getUserProperty(auth, username, "primary-card-number") == card:
25 | print("Removing card number {} from primary card field for user {}".format(card, username))
26 | proxy.api.setUserProperty(auth, username, "primary-card-number", "")
27 | elif proxy.api.getUserProperty(auth, username, "secondary-card-number") == card:
28 | print("Removing card number {} from secondary card field for user {}".format(card, username))
29 | proxy.api.setUserProperty(auth, username, "secondary-card-number", "")
30 | else:
31 | print("Error can't find card number")
32 |
33 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/removeUsersListedInFile/removeUsersListedInFile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Remove PaperCut users if they do not appear in a data file (given in $1)
4 |
5 | server-command list-user-accounts | xargs -n 1 -I {} bash -c "grep {} $1 || server-command delete-existing-user {}"
6 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/removeUsersListedInFile/testData:
--------------------------------------------------------------------------------
1 | alec
2 | fred
3 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/selectPrinterCostModel.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # Find all the users in a specific user group
3 |
4 | import xmlrpc.client
5 | from ssl import create_default_context, Purpose
6 | import sys
7 |
8 | host="https://localhost:9192/rpc/api/xmlrpc" # If not localhost then the client address will need to be whitelisted in PaperCut
9 |
10 | auth="secret" # Value defined in advanced config property "auth.webservices.auth-token". Should be random
11 |
12 | #proxy = xmlrpc.client.ServerProxy(host, verbose=True, transport=xmlrpc.client.SafeTransport, context=ctx)
13 | proxy = xmlrpc.client.ServerProxy(host, verbose=False,
14 | context = create_default_context(Purpose.CLIENT_AUTH))
15 |
16 | serverName = "dipto-Latitude-7490"
17 | printerName = "PDF"
18 | propertyName = "cost-model"
19 | propertyValue = "SIMPLE"
20 |
21 |
22 | if len(sys.argv) != 4:
23 | print("Incomplete. Use: python3 selectPrinterCostModel.py [server_name] [printer_name] [property_value]")
24 | sys.exit(1)
25 | else:
26 | serverName = sys.argv[1]
27 | printerName = sys.argv[2]
28 | propertyValue = sys.argv[3]
29 | print("server-command equivalent: server-command set-printer-property", serverName, propertyName, propertyValue)
30 |
31 | #group = sys.argv[1] # The user group of interest
32 |
33 | try:
34 | command_feedback = proxy.api.setPrinterProperty(auth, serverName, printerName, propertyName, propertyValue)
35 | # print("Got user list {}".format(userList))
36 | except xmlrpc.client.Fault as error:
37 | print("\ncalled setPrinterProperty. Return fault is {}".format(error.faultString))
38 | sys.exit(1)
39 | except xmlrpc.client.ProtocolError as error:
40 | print("\nA protocol error occurred\nURL: {}\nHTTP/HTTPS headers: {}\nError code: {}\nError message: {}".format(
41 | error.url, error.headers, error.errcode, error.errmsg))
42 | sys.exit(1)
43 | except Exception as error:
44 | print("Error", error)
45 | sys.exit(1)
46 |
47 |
48 | print("Feedback from the server :" , command_feedback)
49 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/setAccountSelectionMode.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # For every user in the database, set the account selection mode to be either
4 |
5 | # 1. Auto charge to personal when user has access to zero shared accounts
6 | # 2. Auto charge to shared account when user has access to one shared account
7 | # 3. User standard account selection pop up when user has access to multiple shared accounts
8 |
9 | # This script would need to be run regularly to reflect group changes etc
10 |
11 | import xmlrpc.client
12 | from ssl import create_default_context, Purpose
13 |
14 | host="https://localhost:9192/rpc/api/xmlrpc" # If not localhost then this address will need to be whitelisted in PaperCut
15 | auth="token" # Value defined in advanced config property "auth.webservices.auth-token". Should be random
16 |
17 | proxy = xmlrpc.client.ServerProxy(host, verbose=False,
18 | context = create_default_context(Purpose.CLIENT_AUTH))
19 |
20 | offset = 0
21 |
22 | limit = 100 # Max number of usernames to retrieve on each call
23 |
24 | while True:
25 |
26 | userList = proxy.api.listUserAccounts(auth, offset,limit)
27 |
28 | for user in userList:
29 |
30 | if not proxy.api.isUserExists(auth, user):
31 | print("Can't find user {}".format(user))
32 |
33 | shared_accounts = proxy.api.listUserSharedAccounts(auth, user, 0, 99, True)
34 |
35 | if len(shared_accounts) == 0:
36 | proxy.api.setUserAccountSelectionAutoChargePersonal(auth, user, False)
37 | print("Setting setUserAccountSelectionAutoChargePersonal for user {}".format(user))
38 |
39 | elif len(shared_accounts) == 1:
40 | proxy.api.setUserAccountSelectionAutoSelectSharedAccount(auth, user, shared_accounts[0], False)
41 | print("Setting setUserAccountSelectionAutoSelectSharedAccount for user {} with account {}".format(
42 | user, shared_accounts[0]))
43 |
44 | else:
45 | proxy.api.setUserAccountSelectionStandardPopup(auth, user, False, True, False, False, False)
46 | print("Setting setUserAccountSelectionStandardPopup for user {}".format(user))
47 |
48 | if len(userList) < limit:
49 | break # We have reached the end
50 |
51 | offset += limit # We need to next slice of users
52 |
53 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/setUserProperties.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 |
54 |
55 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/simpleRefundBalance/simpleRefundBalance.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Small webb app to allow a user to request a refund of their personal PaperCut balance
4 | # into another system
5 |
6 | # Add a custom URL to the PaperCut user web page, which is used by end users
7 | # when they want to request a refund from their PaperCut personal account. The url
8 | # should refer to this small web app. When the user clicks on the URL link
9 | # (in the PaperCut user web page) to the web app, the user identification details
10 | # are passed as part of the URL. This is explained at:
11 |
12 | # https://www.papercut.com/products/ng/manual/common/topics/customize-user-web-pages.html#customize-user-web-pages-nav-links
13 |
14 | # The URL neeeds to something like http://localhost:8081/simpleRefundBalance/%user%
15 |
16 | # This code is basic example only. It will require work before it can be used for production
17 |
18 | import xmlrpc.client
19 | import sys
20 | from ssl import create_default_context, Purpose
21 |
22 | # Bottle does not depend on any external libraries.
23 | # You can just download bottle.py into your project directory and using
24 | # $ wget http://bottlepy.org/bottle.py or you can install in the normal fashion
25 | from bottle import route, run, template, request, debug
26 |
27 |
28 | # Prefer HTTPS connection
29 | host="https://localhost:9192/rpc/api/xmlrpc" # If not localhost then this address will need to be whitelisted in PaperCut
30 | auth="token" # Value defined in advanced config property "auth.webservices.auth-token". Should be a long random string
31 |
32 | proxy = xmlrpc.client.ServerProxy(host, verbose=False,
33 | context = create_default_context(Purpose.CLIENT_AUTH))
34 |
35 | @route('/')
36 | def wrongUrl():
37 | return("Please log into PaperCut and chose refund your account balance from there")
38 |
39 | @route('/simpleRefundBalance/')
40 | def promptUser(user):
41 |
42 | if not proxy.api.isUserExists(auth, user):
43 | return("Can't find user {}".format(user))
44 |
45 | userCredit = proxy.api.getUserAccountBalance(auth, user)
46 |
47 | if userCredit <= 0.0 :
48 | return("You currently have a PaperCut balance of {0:.2f} and cannot obtain a refund
".format(userCredit))
49 |
50 | userCredit = "{0:.2f}".format(userCredit)
51 |
52 | userName = proxy.api.getUserProperty(auth, user, "full-name")
53 |
54 | return template('promptForRefund',user=user, userName=userName, userCredit=userCredit)
55 |
56 | @route('/refund/')
57 | def topUp(user):
58 | if request.GET.get('cancel','').strip():
59 | return "Refund cancelled by {}".format(user)
60 |
61 | refundAmount = float(request.GET.get('amount','').strip())
62 |
63 | userCredit = proxy.api.getUserAccountBalance(auth, user)
64 |
65 | if userCredit != refundAmount:
66 | return "Error: User Credit Balance and Refund Requested do not match for user: {}".format(user)
67 |
68 | proxy.api.adjustUserAccountBalance(auth, user, -1 * refundAmount, "Money refunded by the Simple Refund Page")
69 |
70 | return "Updated balance is now {}
Please close this tab/window and return to PaperCut
We would email you at {}, but email it not currently configured
".format(
71 | "{0:.2f}".format(proxy.api.getUserAccountBalance(auth, user)),proxy.api.getUserProperty(auth, user, "email"))
72 |
73 | # now transfer the value to the external student system
74 |
75 | if __name__ == "__main__":
76 | run(host='localhost', port=8081, debug=True, reloader=True)
77 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/simpleRefundBalance/views/promptForRefund.tpl:
--------------------------------------------------------------------------------
1 | %#template to generate a HTML form from a list
2 |
11 |
12 | Brought to you by your local IT Team
13 |
14 | Hello {{userName}}:
15 |
16 | Please confirm that you want you PaperCut MF balance (${{userCredit}}) credited back to your student account
17 |
18 |
26 |
27 | NOTE: After the refund you will be unable to print or use the campus copiers until you credit more funds to your PaperCut MF account
28 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/simpleTopUpBalance/README.md:
--------------------------------------------------------------------------------
1 | # Using the PaperCut MF/NG public web services API to provide a manual top up payment gateway
2 |
3 | The XML-RPC public web services API has a method call `api.adjustUserAccountBalance()` which does what is says on the tin. See the [documentation](http://www.papercut.com/products/ng/manual/apdx-tools-web-services.html) for details.
4 |
5 |
6 | 1. Develop a web page integration with a payment service
7 | 2. Add a custom URL to the PaperCut user web page, which is used by end users when they want to top up their PaperCut account.
8 | 3. When the user clicks on the URL link in the PaperCut user web page to the payment service, the user identification details is passed as part of the URL. This is explained at:
9 |
10 | https://www.papercut.com/support/resources/manuals/ng-mf/common/topics/customize-user-web-pages.html#customize-user-web-pages-nav-links
11 |
12 | 4. The user is re-directed to the custom payment service page via the configured URL. This solution would manage the payment process and, once approved, the solution has the responsibility of calling the local PaperCut XML-RPC service with the user name and credit adjustment `api.adjustUserAccountBalance()`
13 |
14 |
15 | Note: If the XML-RPC call is coming to PaperCut from across the network it is necessary to allow list the remote address in
16 | the PaperCut admin interface.
17 |
18 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/simpleTopUpBalance/requirements.txt:
--------------------------------------------------------------------------------
1 | bottle
2 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/simpleTopUpBalance/simpleTopUpBalance.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Small web app to allow a user to top up their personal PaperCut balance
4 |
5 | # Add a custom URL to the PaperCut user web page, which is used by end users
6 | # when they want to add credit to their PaperCut personal account. The url
7 | # should refer to this small web app When the user clicks on the URL link
8 | # (in the PaperCut user web page) to the web app, the user identification details
9 | # are passed as part of the URL. This is explained at:
10 |
11 | # https://www.papercut.com/products/ng/manual/common/topics/customize-user-web-pages.html#customize-user-web-pages-nav-links
12 |
13 | # The URL neeeds to something like http://localhost:8081/simpleTopUpBalance/?user=%user%&return_url=%return_url%
14 |
15 | # Generally additional security should be provided. For example if the URL is http://localhost:8081/promptForPassword/?user=%user%&return_url=%return_url%
16 | # then the user will need to enter their PaperCut password to access the payment system
17 |
18 | # Handy Tip: By default the link will open in a separate winodow. You can edit the advanced config property user.web.custom-links and
19 | # change "_body" to "_self". You should then use the %return_url% to return the user to the PaperCut MF/NG web interface
20 |
21 | # This code is a basic example only. It should not be used for production
22 |
23 | import xmlrpc.client
24 | import sys
25 | from json import load as loadjs
26 | import logging
27 | import traceback
28 |
29 | # Bottle does not depend on any external libraries.
30 | # You can just download bottle.py into your project directory and using
31 | # $ wget http://bottlepy.org/bottle.py
32 | from bottle import route, run, template, request, debug, response
33 |
34 |
35 | # Prefer HTTPS connection
36 | # If not localhost then this address will need to be whitelisted in PaperCut
37 | host = "http://localhost:9191/rpc/api/xmlrpc"
38 | auth = "token" # Value defined in advanced config property "auth.webservices.auth-token". Should be random
39 |
40 | proxy = xmlrpc.client.ServerProxy(host)
41 |
42 | # For more information on this user database file refer to the custom auth and sync demo
43 | paperCutAccountInfoFile = 'c:\\Program Files\\PaperCut MF\\server\\custom\\config.json'
44 |
45 | paperCutAccountData = {}
46 |
47 | # The user is sent back to the Summary page as if they had just logged in,
48 | # assuming their session has not timed out
49 | # Therefore return url should be consistent
50 | redirect_url = ''
51 |
52 |
53 | @route('/')
54 | def wrongUrl():
55 | return("Please log into PaperCut and set top up your account from there")
56 |
57 |
58 | @route('/promptForPassword/')
59 | def prompForPassword():
60 |
61 | user = request.query.user or ""
62 |
63 | try:
64 | if len(user) == 0 or not proxy.api.isUserExists(auth, user):
65 | return( "Can't find user {}".format(user))
66 | except Exception as e:
67 | logging.error(traceback.format_exc())
68 |
69 | return_url = request.query.return_url or ""
70 |
71 | return template( 'promptForPassword', user=user, return_url=return_url)
72 |
73 | @route('/simpleTopUpBalance/', method='GET')
74 | def promptUser():
75 |
76 | user = request.query.user or ""
77 |
78 | return_url = request.query.return_url or ""
79 |
80 | password = request.query.password or ""
81 |
82 | if paperCutAccountData is None or paperCutAccountData['userdata'][user]['password'] == password:
83 | return template('promptForDeposit',user=user, return_url=return_url)
84 |
85 | # Password validation failed
86 | return template( 'promptForPassword', user=user, error_text="Invalid password entered", return_url=return_url)
87 |
88 |
89 | @route("/topUp/")
90 | def topUp(method="GET"):
91 |
92 | return_url = request.query.return_url or None
93 |
94 | if request.query.cancel == "cancel":
95 | if return_url is None:
96 | return "Cancelled. Please close this tab/window and return to PaperCut"
97 | else:
98 | response.set_header("Refresh", "5; url={}".format(return_url))
99 | return "Cancelled. You will be returned to PaperCut in 5s"
100 |
101 | user = request.query.user
102 |
103 | amount = float(request.query.amount)
104 |
105 | if not amount > 0.0: # Example of data validation -- not used because our form already does this one
106 | return template('promptForDeposit',user=user, return_url=return_url, error_text="Invalid amount \"{}\" entered".format(amount))
107 |
108 | proxy.api.adjustUserAccountBalance(
109 | auth, user, amount, "Money added by the Simple Top Up Page")
110 |
111 | if len(return_url) == 0:
112 | return "Updated balance is now {}
Please close this tab/window and return to PaperCut".format(
113 | proxy.api.getUserAccountBalance(auth,user))
114 |
115 | # Add refresh with 5s timeout back to PaperCut MF/NG
116 | response.set_header("Refresh", "5; url={}".format(return_url))
117 | return "Updated balance is now {}
You will be returned to PaperCcut in 5s".format(
118 | proxy.api.getUserAccountBalance(auth,user))
119 |
120 | try:
121 | with open(paperCutAccountInfoFile) as f:
122 | paperCutAccountData = loadjs(f)
123 | except OSError:
124 | paperCutAccountData = None
125 |
126 | run(host='localhost', port=8081, debug=True, reloader=True)
127 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/simpleTopUpBalance/views/promptForDeposit.tpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
20 |
21 |
22 | Shonky Soft Payment Portal
23 |
24 |
25 | %# Enter a deposit ammount
26 |
27 | % if defined("error_text"):
28 | {{error_text}}
29 | % end
30 |
31 |
32 | Hello {{user}}: please enter the amount you want to deposit into your PaperCut account
33 |
34 | Note: Minimum deposit is 0.05, maximum is 10.00 and only multiples of 0.05 may be entered
35 |
36 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/swapCardNumbers.ps1:
--------------------------------------------------------------------------------
1 | # For dll set-up see
2 | # https://www.papercut.com/kb/Main/AdministeringPaperCutWithPowerShell
3 |
4 | #Import the dlls we need
5 | Add-Type -Path "$env:USERPROFILE\.nuget\packages\kveer.xmlrpc\1.1.1\lib\netstandard2.0\Kveer.XmlRPC.dll"
6 | Add-Type -Path "$PWD\ServerCommandProxy\bin\Release\netstandard2.0\ServerCommandProxy.dll"
7 |
8 | $papercuthost = "localhost" # If not localhost then this address will need to be whitelisted in PaperCut
9 | $auth = "token" # Value defined in advanced config property "auth.webservices.auth-token". Should be random
10 |
11 | # Proxy object to call PaperCut Server API
12 | $s = New-Object PaperCut.ServerCommandProxy($papercuthost, 9191, $auth);
13 |
14 | $BATCH_SIZE = 100
15 |
16 | $(do {
17 | [array]$userList = $s.ListUserAccounts($intCounter, $BATCH_SIZE)
18 | Write-Output $userList
19 | $intCounter += $BATCH_SIZE
20 | } while ($userList.Length -eq $BATCH_SIZE) ) | ForEach-Object {
21 | $cardNumbers = $s.GetUserProperties($_, @("secondary-card-number","primary-card-number"))
22 | $s.SetUserProperties($_, @(@("primary-card-number", $cardNumbers[0]), @("secondary-card-number", $cardNumbers[1])))
23 | }
24 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/swapCardNumbers.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import xmlrpc.client
4 | from ssl import create_default_context, Purpose
5 | import sys
6 |
7 | host="https://localhost:9192/rpc/api/xmlrpc" # If not localhost then this address will need to be whitelisted in PaperCut
8 | auth="token" # Value defined in advanced config property "auth.webservices.auth-token". Should be random
9 |
10 | proxy = xmlrpc.client.ServerProxy(host, verbose=False,
11 | context = create_default_context(Purpose.CLIENT_AUTH))
12 |
13 | offset = 0
14 |
15 | limit = 100 # Max number of usernames to retrieve on each call
16 |
17 | while True:
18 | try:
19 | userList = proxy.api.listUserAccounts(auth, offset,limit)
20 | except xmlrpc.client.Fault as error:
21 | print("\ncalled listUserAccounts(). Return fault is {}".format(error.faultString))
22 | sys.exit(1)
23 | except xmlrpc.client.ProtocolError as error:
24 | print("\nA protocol error occurred\nURL: {}\nHTTP/HTTPS headers: {}\n" +
25 | "Error code: {}\nError message: {}".format(
26 | error.url, error.headers, error.errcode, error.errmsg))
27 | sys.exit(1)
28 | except ConnectionRefusedError as error:
29 | print("Connection refused. Is the Papercut server running?")
30 | sys.exit(1)
31 |
32 | for user in userList:
33 | try:
34 | (primary, secondary) = proxy.api.getUserProperties(auth, user, ("secondary-card-number","primary-card-number"))
35 | proxy.api.setUserProperties(auth, user, (("primary-card-number", primary),("secondary-card-number", secondary)))
36 | except xmlrpc.client.Fault as error:
37 | print("\n{}".format(error.faultString))
38 | sys.exit(1)
39 | except:
40 | print("\nSomething went wrong. Return fault is {}".format(sys.exc_info()[0]))
41 | sys.exit(1)
42 |
43 | if len(userList) < limit:
44 | break # We have reached the end
45 |
46 | offset += limit # We need to next slice of users
47 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/swapCardNumbers.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Swaps the contents of primary and secondary card numbers in the database
4 |
5 | # Sometimes (after you've loaded all the card numbers in PaperCut) you need
6 | # to interface into an external system that requires all the card numbers to
7 | # be swapped between the primary and secondary fields in the PaperCut database,
8 |
9 | # Here is a handy script
10 |
11 | server-command list-user-accounts | while read x;do
12 | primary=$(server-command get-user-property $x "secondary-card-number")
13 | secondary=$(server-command get-user-property $x "primary-card-number")
14 | server-command set-user-property $x "primary-card-number" "$primary"
15 | server-command set-user-property $x "secondary-card-number" "$secondary"
16 | done
17 |
18 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/swapCardNumbersViaServerCommand.ps1:
--------------------------------------------------------------------------------
1 |
2 | Function server-command {
3 | &'C:\Program Files\PaperCut MF\server\bin\win\server-command.exe' $Args
4 | }
5 |
6 | server-command list-user-accounts | ForEach-Object {
7 | $primary=(server-command get-user-property $_ "secondary-card-number")
8 | $secondary=(server-command get-user-property $_ "primary-card-number")
9 | server-command set-user-property $_ "primary-card-number" "$primary"
10 | server-command set-user-property $_ "secondary-card-number" "$secondary"
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/topUpOverDrawnAccounts.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Find all users with a negative balance and top up there account to zero.
4 | # Normally you would expect to get funds from an external payment provider
5 |
6 | # Note: This is for illustrative purposes only. In production we would recommend
7 | # using the web services API for this type of payment feature.
8 |
9 | # Instead of toppingup the account you may care to send the user
10 | # a reminder to top up their payment purse as soon as possible. Remember
11 | # you cant get a user's email address with the user properties call.
12 |
13 | SERVER_COMMAND=~papercut/server/bin/mac/server-command
14 |
15 | "${SERVER_COMMAND}" list-user-accounts | while read user ; do
16 | balance=$("${SERVER_COMMAND}" get-user-property $user balance)
17 | if [[ $balance < 0 ]] ; then
18 | # Might need to add a check to see if the user needs to be topped up.
19 | # Perhaps by checking group membership via get-user-groups or
20 | # by checking user's overdraft via get-user-property.
21 |
22 | # Should request funds from external source here
23 | topUp=$(echo "$balance * -1" | bc -q) # make it positive
24 | echo $user has -ve balance $balance about to top up user account with value $topUp
25 | "${SERVER_COMMAND}" adjust-user-account-balance $user $topUp "Remove -ve Balance"
26 | fi
27 | done
28 |
29 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/useHealthAPI.ps1:
--------------------------------------------------------------------------------
1 | # Report some simple PaperCut MF/NF information using the health API
2 |
3 | # Setup and use the PaperCut web services API. More information at
4 | # https://www.papercut.com/kb/Main/AdministeringPaperCutWithPowerShell
5 | Add-Type -Path "$env:USERPROFILE\.nuget\packages\kveer.xmlrpc\1.1.1\lib\netstandard2.0\Kveer.XmlRPC.dll"
6 | Add-Type -Path "$PWD\ServerCommandProxy\bin\Release\netstandard2.0\ServerCommandProxy.dll"
7 |
8 | # Create a proxy object so we can call web services API XML-RPC methods
9 | $s = New-Object PaperCut.ServerCommandProxy("localhost",9191,"token")
10 |
11 | # Find the value of the Auth key in the PaperCut MF/NG config database
12 | $authString = $s.GetConfigValue("health.api.key")
13 |
14 | # Set up the http header for the health API
15 | $headers = @{'Authorization' = $authString}
16 |
17 | $uri = [Uri]"http://localhost:9191/api/health"
18 |
19 | # Generate the report
20 | &{
21 | # Get a list of the processes running
22 | Get-Service -DisplayName *PaperCut* | Select-Object -Property name,status |
23 | ConvertTo-Html -Fragment -PreContent 'PaperCut Processes
'
24 |
25 |
26 | $rsp = Invoke-RestMethod -Uri $uri -Method Get -Headers $headers
27 |
28 |
29 | # $rsp.applicationServer.systemInfo |
30 | # ConvertTo-Html -As List -Fragment -PreContent 'PaperCut Services Info
'
31 |
32 | $rsp.license.devices | ConvertTo-Html -As List -Fragment -PreContent 'License Info
'
33 | # $rsp.devices | ConvertTo-Html -As List -Fragment -PreContent 'Device Info
'
34 |
35 |
36 | # Write-Output "Total Printers = $($rsp.printers.count)
"
37 | # Write-Output "Total Devices = $($rsp.devices.count)
"
38 | } | Out-File -FilePath .\report1.html
39 |
40 |
41 | Invoke-Expression .\report1.html
42 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/userSelectSharedAccount/userSelectSharedAccount.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Small webb app to allow a user to change their shared account
4 |
5 | # Add a custom URL to the PaperCut user web page, which is used by end users
6 | # when they want to change their default PaperCut shared account. The url
7 | # should refer to this small web app When the user clicks on the URL link
8 | # (in the PaperCut user web page) to the web app, the user identification details
9 | # is passed as part of the URL. This is explained at:
10 |
11 | # https://www.papercut.com/support/resources/manuals/ng-mf/common/topics/customize-user-web-pages.html#customize-user-web-pages-nav-links
12 |
13 | # The URL neeeds to something like http://localhost:8080/getsharedaccountselection/%user%
14 |
15 | # This code is basic example only. It will require work before it can be used for production
16 |
17 | import xmlrpc.client
18 | import sys
19 |
20 | # Bottle does not depend on any external libraries.
21 | # You can just download bottle.py into your project directory and using
22 | # $ wget http://bottlepy.org/bottle.py
23 | from bottle import route, run, template, request, debug
24 |
25 | host="http://localhost:9191/rpc/api/xmlrpc" # If not localhost then this address will need to be whitelisted in PaperCut
26 | auth="password" # Value defined in advanced config property "auth.webservices.auth-token". Should be random
27 |
28 | proxy = xmlrpc.client.ServerProxy(host)
29 |
30 | @route('/')
31 | def wrongUrl():
32 | return("Please log into PaperCut and set your shared account from there")
33 |
34 | @route('/getsharedaccountselection/')
35 | def setSharedAccount(user):
36 |
37 | if not proxy.api.isUserExists(auth, user):
38 | return("Can't find user {}".format(user))
39 |
40 | shared_accounts = proxy.api.listUserSharedAccounts(auth, user, 0, 99, True)
41 |
42 | if len(shared_accounts) == 0:
43 | return "User {} has no access to shared accounts".format(user)
44 |
45 | return template('displayAccounts',user=user,rows=shared_accounts)
46 |
47 |
48 | @route('/setdefaultsharedaccount/')
49 | def changeAccountTo(user):
50 | if request.GET.get('cancel','').strip():
51 | return "Cancelled"
52 |
53 | selectedAccount = request.GET.get('account','').strip()
54 |
55 | proxy.api.setUserAccountSelectionAutoSelectSharedAccount(auth,user,selectedAccount, False)
56 |
57 | return 'Changed default account to "{}"
Please close this tab/window and return to PaperCut'.format(selectedAccount)
58 |
59 | run(host='localhost', port=8080, debug=True, reloader=True)
60 |
61 |
--------------------------------------------------------------------------------
/PublicWebServicesAPI_AND_servercommandScripts/userSelectSharedAccount/views/displayAccounts.tpl:
--------------------------------------------------------------------------------
1 | %#template to generate a HTML form from a list
2 |
3 | Shared Accounts for {{user}}:
4 |
5 |
13 |
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PaperCutExamples
2 |
3 | We recommend joining the mail support group at
4 | https://groups.google.com/forum/#!forum/papercut-webservices-api
5 |
6 | This repository contains various examples of scripts and other things that PaperCut users may find useful.
7 |
8 | It can be broadly grouped into the following things:
9 |
10 | 1. SQL report examples
11 | 2. Programs and scripts that use the PaperCut web services API, from simple to complex
12 | 3. An example of how to use the custom authentication and user account synchronization CLI
13 | 4. Some example printer scripts
14 | 5. An example of a quick-release terminal
15 |
16 | PaperCut MF (and PaperCut NG) is a powerful Copier and Printer management system. More details at http://papercut.com/
17 |
18 | All the files in this repository come with ABSOLUTELY NO WARRANTY and in the hope that they will be useful.
19 | This is free software, and you are welcome to redistribute it under the terms of The MIT License,
20 | refer to the file license for more details.
21 |
22 | ## Please note:
23 |
24 | The files in this repository have been contributed by many different members of the PaperCut
25 | community, business partners, and customers. We believe that all the examples should work as "advertised",
26 | however specific circumstances in your environment may mean that something does not perform as anticipated.
27 | __Please make sure that you test adequately in your environment__.
28 |
29 | You can find other examples in the PaperCut [Knowledge Base](https://www.papercut.com/kb/Category/API)
30 |
31 | ## Contributions Welcome
32 |
33 | Please feel free to send us pull requests, or if you are not comfortable pull requests, send changes and
34 | additions to integration-dev-support@papercut.com. We will of course credit you and you get bragging rights.
35 |
36 | Copyright (C) 2015-2017 PaperCut Software Pty Ltd.
37 |
--------------------------------------------------------------------------------
/Reports/README.md:
--------------------------------------------------------------------------------
1 | The material has moved to https://github.com/PaperCutSoftware/MF-NG-Report-Examples
--------------------------------------------------------------------------------
/TopUpCards/README.md:
--------------------------------------------------------------------------------
1 | # Creating PaperCut NG top up cards
2 |
3 | Using [top up cards](https://www.papercut.com/kb/Main/TopUpCards) is a great "low tech" solution for students to get cash into their PaperCut personal account. PaperCut does supply an MS Word tools to create the import file,
4 | which is designed to work on Windows using MS Office.
5 | You may prefer to create your own tool to generate the cards and the import file.
6 |
7 | You'll need to create a process (this may involve creating a small tool) that generates
8 |
9 | 1. A print file of cards, each of which contains the correct card numbers and other data. These can then be printed and sold to students
10 |
11 | 2. A data file that has a record for each card you are printing out. This file format is as follows.
12 |
13 | File must have the following data encoding
14 | UTF-16 text with MS Windows line endings. The file must have the correct BOM (https://en.wikipedia.org/wiki/Byte_order_mark)
15 |
16 | First line is a mandatory header line with the following fixed content
17 |
18 | ```
19 | CardNumber,BatchID,ValueNumber,Value,ExpDate,ExpDateDisplay
20 | ```
21 |
22 | Notice. Field Separator is “,”. There are NO quotes around any of the text
23 |
24 | Each following data line must look like this
25 |
26 | ```
27 | "P-1503-BLNZ-YTSF","P-1503","10","$10.00","2015-09-13","13/09/2015"
28 | ```
29 |
30 | Notice: All fields are text, Field Separator is “,”. Quotes around text are mandatory
31 |
32 | The values for the columns "Value" and "ExpDateDisplay" should be in the correct format for your locale
33 |
34 | If you want to create a test file manually you can use the following tools
35 | * Libreoffice Spreadsheet can save files as UTF-16 CSV file
36 | * Using an editor such as vim to remove quotes on header line, if you want to get really fancy you can use a tool such as sed.
37 |
38 | If your file is not in MS Windows format use dos2unix tools to create MS Windows format e.g. unix2dos -f -u -b file.csv
39 |
40 | Once you understand the process then you can consider creating a standalone utility to create batches of cards.
41 | The program [createTopUpCards.py](createTopUpCards.py) is a working exampe of such a utility that
42 | you will need to modify for your locale and requirements.
43 |
--------------------------------------------------------------------------------
/TopUpCards/createTopUpCards.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # Creates:
3 | # Print file for cards
4 | # Import file for PaperCut NG
5 |
6 | # For more details about top up cards see https://www.papercut.com/kb/Main/TopUpCards
7 |
8 | # Note: Need to install reportlab module
9 |
10 |
11 | from datetime import date, timedelta
12 | from random import sample, seed
13 | from pdfdocument.document import PDFDocument
14 | import locale
15 | import os
16 |
17 | #Modify to suite
18 | locale.setlocale(locale.LC_ALL, (os.environ["LANG"], "UTF-8")
19 |
20 |
21 | template = """
22 | Value: {}
23 |
24 | Expiry Date: {}
25 |
26 | Batch ID:\t{}
27 | Card ID:\t{:09d}
28 |
29 | """
30 |
31 | # Initialise the random number generator
32 |
33 | seed()
34 |
35 | batchID = input("Please enter the batch ID ")
36 | cardValue = float(input(f'Please enter value to appear on each card '))
37 | numberOfCards = int(input(f'Please enter number of cards to created in batch "{batchID}" '))
38 | expiryDays = int(input("How many days are these cards valid (PaperCut NG will not except these cards after this period)"))
39 |
40 |
41 | pdf = PDFDocument("printFile.pdf")
42 |
43 | pdf.init_report()
44 |
45 | # Must be UTF-16 with correct BOM and MD-DOS line endings
46 | importFile = open("importFile", mode="w", newline="\r\n", encoding='utf-16')
47 |
48 | # PaperCut needs this header line in this format
49 | importFile.write("CardNumber,BatchID,ValueNumber,Value,ExpDate,ExpDateDisplay\n")
50 |
51 | # Modify to as needed
52 | expDate = date.today() + timedelta(days=14) # expire all the cards -- default to 14 days
53 | displayExpDate = expDate.strftime("%x")
54 | isoExpDate = expDate.isoformat()
55 |
56 | displayValue = locale.currency( cardValue, grouping=True )
57 |
58 | for cardNumber in sample(range(100000000), numberOfCards):
59 |
60 | importFile.write(
61 | f'"{batchID}-{cardNumber:09d}","{batchID}","{cardValue}","{displayValue}","{isoExpDate}","{displayExpDate}"\n')
62 |
63 | pdf.start_keeptogether(); # Make sure cards are not split across page boundaries
64 | pdf.h1("PaperCut NG Top Up Card")
65 | pdf.p(template.format(displayValue, displayExpDate, batchID, cardNumber))
66 | pdf.end_keeptogether()
67 |
68 | pdf.generate()
69 | importFile.close()
70 |
--------------------------------------------------------------------------------
/fastReleaseProtocol/NetworkCardReaderFastRelease.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # (c) Copyright 2014-15 PaperCut Software International Pty Ltd.
3 | #
4 | # An example Python script demonstrating how to implement the Card Reader Fast Release
5 | # protocol for PaperCut
6 |
7 | # Setup: In the PaperCut Admin interface setup a supported MFD device with
8 | # a network card reader. A fast release terminal may be used instead
9 |
10 | # Note that this is an example implementation only and not intended for
11 | # production use. Please consult the PaperCut Knowledge Base or contact
12 | # PaperCut support for assistance
13 |
14 | import socket
15 | import sys
16 |
17 | HOST = "0.0.0.0" # Bind on any address
18 | PORT = 7778 # Default port on PaperCut server for the card reader
19 |
20 |
21 | # Some example accounts
22 | userCredentialsDB = {
23 | "Alec" : "ed5d34c74e59d16bd",
24 | "Chris": "5678",
25 | "Julie": "EMP123",
26 | }
27 |
28 | cardNumber = userCredentialsDB["Alec"] # Change to suite
29 |
30 |
31 | versionString = "Custom Python Script"
32 |
33 |
34 | try:
35 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
36 | # print("Returned from socket creation")
37 | s.bind((HOST, PORT))
38 | # print("Returned from bind")
39 | s.listen(1)
40 | # print("Returned from listen")
41 | except socket.error, msg:
42 | print("Socket error on socket.bind {}".format(msg))
43 | print('could not open socket')
44 | s.close()
45 | sys.exit(1)
46 |
47 |
48 | try:
49 | conn, addr = s.accept() # Good implementation would only accept from PaperCut server IP
50 | print('Connected by', addr)
51 |
52 | data = conn.recv(50) # Throw '\r' away
53 | print("I got \"{}\". Will Throw away".format(repr(data)))
54 | data = conn.recv(50) # Throw rfid prompt away away
55 | print("I got \"{}\". Will Throw away".format(repr(data)))
56 |
57 | except:
58 | conn.close()
59 | print("Error in connection attempt")
60 |
61 | while 1:
62 |
63 | data = conn.recv(50).rstrip() # throw away trailing whitepace
64 | if not data: # We got a reset. Send a card number
65 | raw_input("Please press enter key to simulate card being swiped")
66 | print("Sending card number \"{}\"".format(cardNumber))
67 | conn.send(cardNumber+'\n')
68 | else:
69 | r = data[-1] # get last command char
70 | print("Received command from server \"{}\"".format(r))
71 |
72 | if r == 'v': # Request for version number
73 | conn.send("{}\r".format(versionString)) #PaperCut will log this
74 | print("Sent version string \"{}\"".format(versionString))
75 | elif r == 'e': # Request to flag an error to user (e.g. Unknown card)
76 | print("Error Indicator On") # e.g. flash red LED
77 | elif r == 'b': # Request make a audible indication (may happen multiple times
78 | print("Beep sounded") # on a single card swipe)
79 | # Endwhile
80 |
81 | conn.close()
82 |
83 |
84 |
--------------------------------------------------------------------------------
/mf-automation/do-we-need-to-upgrade.ps1:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env pwsh
2 |
3 | # 1. Discover the latest PaperCut MF release version fron the Atom webste feed
4 | # 2. Discover what version of PaperCut MF we are running
5 | # 3. If they are not the same then create a job ticket, but only if we have not seen this release before
6 |
7 | # PaperCut version are in semver format, which PowerShell supports via the .NET System.Version type
8 |
9 | $MF_HOST = "$(hostname).local" # Modify to suit
10 | $PRODUCT = "mf" # Changing to "ng" should work, but has not been tested
11 | $GH_REPO = "PaperCutSoftware/PaperCutExamples"
12 | $MINION = "alecthegeek" # Assignee for review tickets
13 |
14 | $HEALTH_API_KEY = ""
15 |
16 | # Assume we are on the PaperCut MF server and use server-command (NB Need to run in elevated shell for this to work)
17 | $HEALTH_API_KEY = `
18 | & "$((Get-ItemProperty -Path 'HKLM:\HKEY_LOCAL_MACHINE\SOFTWARE\PaperCut MF').InstallPath)\server\bin\win\server-command.exe" `
19 | get-config "health.api.key"
20 |
21 | if ($LASTEXITCODE -ne 0 ) {
22 |
23 | # Server command did not work. Use the web services API
24 |
25 | # Need a web services API token.Get this from your local PaperCut admin
26 | $API_TOKEN = (Get-Content -raw ~/.PAPERCUT_API_TOKEN).trim() # Don't hard code API tokens
27 |
28 | $HEALTH_API_KEY = (@"
29 |
30 |
31 | api.getConfigValue
32 |
33 |
34 | $API_TOKEN
35 |
36 |
37 | health.api.key
38 |
39 |
40 |
41 | "@ | Invoke-RestMethod -Method 'Post' -Uri "http://${MF_HOST}:9191/rpc/api/xmlrpc" | Select-Xml -XPath "/methodResponse/params/param/value").toString()
42 |
43 |
44 | }
45 |
46 | if ($HEALTH_API_KEY.Length -eq 0 )
47 | {
48 | Write-Host Could not retrieve health API key
49 | Exit-PSHostProcess 1
50 | }
51 |
52 | $URI = [Uri]"http://${MF_HOST}:9191/api/health"
53 |
54 | $rsp = Invoke-RestMethod -Uri $URI -Method Get -Headers @{ 'Authorization' = $HEALTH_API_KEY }
55 |
56 | $INSTALLED_RELEASE = [System.Version]($rsp.applicationServer.systemInfo.Version -replace '^([\.\d]+).+','$1')
57 |
58 | # Get the latest release from the PaperCut website (parse the XML Atom feed)
59 | $CURRENT_RELEASE = [System.Version]((Invoke-RestMethod -uri http://www.papercut.com/products/mf/release-history.atom).id[0] `
60 | -replace "^tag:papercut.com,[0-9]+-[0-9]+-[0-9]+:$PRODUCT\/releases\/v(\d+)-(\d+)-(\d+)",'$1.$2.$3')
61 |
62 | Write-Host "Latest PaperCut release is $CURRENT_RELEASE. Installed Release is $INSTALLED_RELEASE"
63 |
64 | try {
65 | $LAST_VERSION_CHECKED = Import-Clixml -path ~/LAST_VERSION_CHECKED
66 | }
67 | catch {
68 | $LAST_VERSION_CHECKED = $INSTALLED_RELEASE
69 | }
70 |
71 | if ( "$LAST_VERSION_CHECKED" -eq "$CURRENT_RELEASE" ) {
72 | Write-Host PaperCut $PRODUCT_UPCASE $CURRENT_RELEASE already checked. Nothing to do here
73 | Exit-PSHostProcess 0
74 | }
75 |
76 | if ( "$CURRENT_RELEASE" -eq "$INSTALLED_RELEASE") {
77 | Write-Host Installed release $INSTALLED_RELEASE is already up to date. No new update available
78 | Exit-PSHostProcess 0
79 | }
80 |
81 | Write-Host PaperCut $PRODUCT.ToUpper() $INSTALLED_RELEASE.ToString() can be upgraded to $CURRENT_RELEASE.ToString()
82 |
83 | $RELEASE_NOTES="https://www.papercut.com/products/$PRODUCT/release-history/$($CURRENT_RELEASE.MAJOR)-$($CURRENT_RELEASE.MINOR)/#v$($CURRENT_RELEASE.MAJOR)-$($CURRENT_RELEASE.MINOR)-$($CURRENT_RELEASE.BUILD)"
84 |
85 | # Use appropriate API for your ticket system
86 | $env:GH_TOKEN = (Get-Content -raw ~/.GITHUB_ACCESS_TOKEN).trim()
87 |
88 | gh issue -R "$GH_REPO" create -a "$MINION" `
89 | -t "Review PaperCut $($PRODUCT.ToUpper()) $($INSTALLED_RELEASE.ToString()) upgrade to version $($CURRENT_RELEASE.ToString())" `
90 | -b "Review release notes at $RELEASE_NOTES"
91 |
92 | Export-Clixml -path ~/LAST_VERSION_CHECKED -InputObject $CURRENT_RELEASE
93 |
--------------------------------------------------------------------------------
/mf-automation/do-we-need-to-upgrade.sh:
--------------------------------------------------------------------------------
1 | #!/bin/dash
2 |
3 | # Use the Dash shell. It's smaller and faster
4 |
5 | # 1. Discover the latest PaperCut MF release version fron the Atom feed
6 | # 2. Discover what version of PaperCut MF we are running
7 | # 3. If they are not the same then create a job ticket, but only if we have not seen this release before
8 |
9 | # This example uses xmllint, jq and the glab (the GitLab command line client). They will need to be installled
10 |
11 | MF_HOST="$(hostname).local" # Modify to suit
12 | PRODUCT=mf # Changing to "ng" should work, but has not been tested
13 | PRODUCT_UPCASE=$(echo $PRODUCT | tr [a-z] [A-Z]) # Sometimes we need upper case version
14 | GH_REPO="PaperCutSoftware/PaperCutExamples"
15 |
16 | # ID of system admin to review releases
17 | MINION=alecthegeek
18 |
19 |
20 | # We need the config health.api.key value
21 | # Assume we are on the PaperCut MF server and use server-command (NB Needs to run under the correct user)
22 | if ! HEALTH_API_KEY=$(~papercut/server/linux-x64/bin/server-command get-config-value health.api.key) ; then
23 |
24 | # Can't use server-command. Get via it the web services API.
25 |
26 | # Need a web services API token.Get this from your local PaperCut admin
27 | API_TOKEN="$(cat ~/.PAPERCUT_API_TOKEN)" # Dont' hard code API tokens
28 |
29 | HEALTH_API_KEY=$(curl -s -H "content-type:text/xml" "http://${MF_HOST}:9191/rpc/api/xmlrpc" --data @- <
31 |
32 | api.getConfigValue
33 |
34 |
35 | ${API_TOKEN}
36 |
37 |
38 | health.api.key
39 |
40 |
41 |
42 | EOF
43 | )
44 | fi
45 |
46 | if [ -z "$HEALTH_API_KEY" ] ; then
47 | echo HEALTH_API_KEY not found
48 | exit 1
49 | fi
50 |
51 | INSTALLED_RELEASE=$(curl -s -H "Authorization:$HEALTH_API_KEY" "http://${MF_HOST}:9191/api/health" | jq '.applicationServer.systemInfo.version' |sed -Ee 's/^"([0-9]+\.[0-9]+\.[0-9]+).+$/\1/')
52 |
53 | # Discover the latest PaperCut MF release from the papercut.com atom feed
54 | CURRENT_RELEASE=$(curl -sL http://www.papercut.com/products/$PRODUCT/release-history.atom |
55 | xmllint --xpath "//*[local-name()='feed']/*[local-name()='entry'][1]/*[local-name()='id']/text()" - |
56 | sed -Ene 's/tag:papercut.com,[0-9]{4}-[0-9]{2}-[0-9]{2}:'$PRODUCT'\/releases\/v([0-9]+)-([0-9]+)-([0-9]+)/\1.\2.\3/gp')
57 |
58 | echo Latest PaperCut release is $CURRENT_RELEASE. Installed Release is $INSTALLED_RELEASE
59 |
60 | #Default
61 | LAST_VERSION_CHECKED=$INSTALLED_RELEASE
62 |
63 | if [ -e ~/LAST_VERSION_CHECKED ] ;then
64 | LAST_VERSION_CHECKED=$(cat ~/LAST_VERSION_CHECKED) # Override default
65 | fi
66 |
67 | if [ "$LAST_VERSION_CHECKED" = "$CURRENT_RELEASE" ] ; then
68 | echo PaperCut $PRODUCT_UPCASE $CURRENT_RELEASE already checked. Nothing to do here
69 | exit 0
70 | fi
71 |
72 | if [ "$INSTALLED_RELEASE" = "$CURRENT_RELEASE" ] ; then
73 | echo Installed release $INSTALLED_RELEASE is already up to date. No new update available
74 | exit 0
75 | fi
76 |
77 | echo PaperCut $PRODUCT_UPCASE $INSTALLED_RELEASE can be upgraded to $CURRENT_RELEASE
78 |
79 | MAJOR="$(echo $CURRENT_RELEASE | cut -d . -f 1)"
80 | MINOR="$(echo $CURRENT_RELEASE | cut -d . -f 2)"
81 | FIX="$(echo $CURRENT_RELEASE | cut -d . -f 3)"
82 |
83 | RELEASE_NOTES="https://www.papercut.com/products/$PRODUCT/release-history/${MAJOR}-${MINOR}/#v${MAJOR}-${MINOR}-${FIX}"
84 |
85 | # Use appropriate API for your ticket system
86 | GH_TOKEN=$(cat ~/.GITHUB_ACCESS_TOKEN) gh issue -R $GH_REPO create -a $MINION \
87 | -t "Review PaperCut $PRODUCT_UPCASE $INSTALLED_RELEASE upgrade to version $CURRENT_RELEASE" \
88 | -b "Review release notes at $RELEASE_NOTES"
89 |
90 | echo -n $CURRENT_RELEASE > ~/LAST_VERSION_CHECKED
91 |
--------------------------------------------------------------------------------
/print-deploy-bulk-scripts/README.md:
--------------------------------------------------------------------------------
1 | # Introduction #
2 | These Python scripts can be used to create zones in Print Deploy and assign print queues to these zones.
3 |
4 | ### Requirements ###
5 | * [Python3](https://www.python.org/downloads/)
6 | * [Python requests](https://pypi.org/project/requests/)
7 |
8 |
9 | # How to create zones #
10 | See the example zones.csv file.
11 |
12 | Don't edit the headings.
13 |
14 | * Zone Name: The name that will appear in the admin interface
15 | * Display Name (Optional): Optional display name for users. If this column is left blank, then users will be presented with the Zone Name instead.
16 | * Groups (Optional): If left blank, then this zone will be available to all groups. If more than one group is needed, then use a comma seperated list within quotes. For example: "group1,group2".
17 | * IP Range (Optional): If left blank, then this zone will be available to all IP addresses. If more than one range is needed, then use a comma seperated list within quotes. For example: "192.168.1.1-192.168.1.10,192.168.0.0/24".
18 | * Hostname Regex: Hostname regex string. See example strings [here](https://www.papercut.com/help/manuals/print-deploy/set-up/add-zones-user-groups/)
19 |
20 |
21 | ### How to run script ###
22 | Example for Mac/Linux: `python3 ./create_zones.py zones.csv --username admin --password password --host localhost --port 9192 -e`
23 | Example for Windows: `create_zones.py zones.csv --username admin --password password --host localhost --port 9192 -e`
24 |
25 | ### Positional Arguments ###
26 |
27 | | Argument | Description |
28 | | -------------- | ------------------------------------ |
29 | | `csv_file_path`| The CSV input file to process |
30 |
31 | ### Optional Arguments ###
32 |
33 | | Option | Description |
34 | | ------------------------------- | --------------------------------------------------------------------------- |
35 | | `-e`, `--edit` | If `-e` or `--edit` argument is present, then zones will be updated with the CSV configuration. Otherwise the existing zones will be skipped. |
36 | | `-p`, `--password` | Password for authentication. If not set, then 'password' is used. |
37 | | `-u`, `--username` | Username for authentication. If not set, then 'admin' is used. |
38 | | `-P`, `--port` | Port number for the connection. If not set, then '9192' is used. |
39 | | `--host` | Host address for the connection. If not set, then 'localhost' is used. |
40 |
41 | # How to assign print queues to zones #
42 | Reference the zones.csv exmaple in this repo, or download your current zones and print queues to a CSV file by running:
43 | `python3 assign_print_queues_to_zones.py --output output.csv --username admin --password password --host localhost --port 9192`
44 |
45 | This output CSV file will be compatible to import again. At the bottom of the CSV file print queues will be listed that are not yet connected to any zones. To assign them to zones, simply fill in the zone name and then run:
46 |
47 | `python3 assign_print_queues_to_zones.py -f input.csv --edit --username admin --password password --host localhost --port 9192` (In this example, the input file was renamed to input.csv)
48 |
49 | Don't edit the headings for the following columns:
50 |
51 | * Zone Name: Which zone to connect the print queue to.
52 | * Print Queue: The print queue name to connect
53 | * Optional: If set to 'true', then this print queue will be connected as optional to the zone.
54 | * Default: If set to 'true', this printer will be the zone's default printer and any previously default printers will be cleared.
55 |
56 | ### How to run script ###
57 | Example when importing from a CSV file. This example will update the Optional value if the print queue was already deployed: `python3 assign_print_queues_to_zones.py --username admin --password password --host localhost --port 9192 -f printers_to_zones_example.csv --edit`
58 |
59 | ### Optional Arguments ###
60 | Choose whether you'll import the print queue to zone assignment from a CSV, or provide a single print queue via arguments.
61 |
62 | | Option | Description |
63 | |-----------------------------|-------------------------------------------------------------------------------------------------------|
64 | | `-p, --password` | Password for authentication |
65 | | `-u, --username` | Username for authentication |
66 | | `-P, --port` | Port number for the connection |
67 | | `--host` | Host name for the connection |
68 | | `-f, --file` | The CSV input file to process. |
69 | | `--output` | Download CSV file with current print queue assignment. Useful to edit and import again. |
70 | | `--printer` | If `--printer` argument is present, a single printer will be assigned to the zone specified by `--zone`. |
71 | | `-z, --zone` | The zone the single printer (`--printer`) will be connected to. |
72 | | `-o, --optional` | If set, then the single printer (`--printer`) will be connected to the zone (`--zone`) as optional. |
73 | | -d, --default | If set, this printer will be the zone's default printer and any previously default printers will be cleared. |
74 | | `-e, --edit` | If set, then the optional flag will be updated if a print queue is already deployed to a zone. |
75 |
--------------------------------------------------------------------------------
/print-deploy-bulk-scripts/assign_print_queues_to_zones.py:
--------------------------------------------------------------------------------
1 | #
2 | # (c) Copyright 1999-2024 PaperCut Software International Pty Ltd.
3 | #
4 |
5 | import requests
6 | import csv
7 | import argparse
8 | import sys
9 | import json
10 |
11 |
12 | #Global variables
13 | session = None
14 | printers_ids = None
15 | zones = None
16 |
17 |
18 | # Suppress warnings for self-signed certificates. Remove this code in environments where CA-signed certificates are used.
19 | import urllib3
20 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
21 |
22 | #The script will default to these values are not specified. Don't edit these in the script, rather provide them through input arguments. Run "python 3 create_zones.py --help"
23 | host_name = 'localhost'
24 | port = 9192
25 | password = "password"
26 | username = "admin"
27 |
28 | def login(username,password):
29 | #Get SessionID
30 | global session
31 | session = requests.Session()
32 | # First request to get the session cookies
33 | response = session.get("https://{}:{}/admin".format(host_name,port), verify=False)
34 |
35 | # Define the URL
36 | url = 'https://{}:{}/app'.format(host_name,port)
37 |
38 | headers = {
39 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
40 | "Content-Type": "application/x-www-form-urlencoded",
41 | "Host": "{}:{}".format(host_name,port),
42 | "Referer": "https://{}:{}/admin".format(host_name,port),
43 | }
44 |
45 |
46 | # Define the request body
47 | body = {
48 | "service": "direct/1/Home/$Form",
49 | "sp": "S0",
50 | "Form0": "$Hidden$0,$Hidden$1,inputUsername,inputPassword,$Submit$0,$PropertySelection",
51 | "$Hidden$0": "true",
52 | "$Hidden$1": "X",
53 | "inputUsername": username,
54 | "inputPassword": password,
55 | "$Submit$0": "Log in",
56 | "$PropertySelection": "en"
57 | }
58 |
59 | response = session.post(url, headers=headers, data=body, verify=False)
60 |
61 | # Check the response
62 | if response.status_code == 200 or response.status_code == 302:
63 | # Check if the response contains the string "Login"
64 | if "Login" in response.text:
65 | print("Username or password not correct")
66 | return False
67 | else:
68 | print("Login was successful")
69 | return True
70 | else:
71 | print("Request failed with status code %s" % response.status_code)
72 | return False
73 |
74 |
75 | def get_data(path):
76 |
77 | #############
78 |
79 | # Define the URL
80 | url = 'https://{}:{}{}'.format(host_name,port,path)
81 |
82 | # Define the headers
83 | headers = {'Content-Type': 'application/json'}
84 |
85 | # Send the POST request
86 | response = session.get(url, headers=headers, verify=False)
87 | # Check the response
88 | if response.status_code == 200:
89 | return response.json()
90 | else:
91 | print("Request to '{}' failed with status code {}".format(path,response.status_code))
92 | return False
93 |
94 | def post_data(path,data):
95 | #############
96 |
97 | # Define the URL
98 | url = 'https://{}:{}{}'.format(host_name,port,path)
99 |
100 | # Define the headers
101 | headers = {'Content-Type': 'application/json'}
102 |
103 | # Send the POST request
104 | response = session.post(url, headers=headers, data=json.dumps(data), verify=False)
105 | if response.status_code == 200 or response.status_code == 302 or response.status_code == 201:
106 | return True
107 | else:
108 | print("Request to '{}' failed with status code {}".format(path,response.status_code))
109 | return False
110 |
111 | # Assign print queue to Zone
112 | def connect_print_queue_to_zone(zone_name, new_print_queue_name, optional, default, edit=False):
113 | global zones
114 | global printers_ids
115 |
116 | for zone in zones:
117 | if zone["name"] == zone_name:
118 | print("Found zone {}".format(zone_name))
119 | zone_id = zone["id"]
120 | zone_print_queues = zone["zonePrintQueues"]
121 | is_print_queue_already_connected = False
122 |
123 | # Check if the print queue already exists in the zone
124 | for i, print_queue in enumerate(zone_print_queues):
125 | if print_queue.get("key") == printers_ids.get(new_print_queue_name):
126 | is_print_queue_already_connected = True
127 | if edit:
128 | if default:
129 | #Clear default from all other printers in zone before we set default on this printer.
130 | for printer in zone_print_queues:
131 | printer['defaultPrinter'] = False
132 | # Replace the matching print queue
133 | print("Editing printer {} in {}.".format(new_print_queue_name, zone_name))
134 | zone_print_queues[i] = {'key': printers_ids[new_print_queue_name], 'optional': optional, 'defaultPrinter': default}
135 | else:
136 | print("Printer {} is already deployed to {}.".format(new_print_queue_name, zone_name))
137 | break
138 |
139 | if not is_print_queue_already_connected:
140 | if default:
141 | #Clear default from all other printers in zone before we set default on this printer.
142 | for printer in zone_print_queues:
143 | printer['defaultPrinter'] = False
144 | print("Deploying printer {} to {}.".format(new_print_queue_name, zone_name))
145 | zone_print_queues.append({'key': printers_ids[new_print_queue_name], 'optional': optional, 'defaultPrinter': default})
146 |
147 | payload = {"printQueues": zone_print_queues}
148 | post_data("/print-deploy/admin/api/zones/{}/printQueues".format(zone_id), payload)
149 | break
150 |
151 | #Export current print queue to zone config
152 | def export_print_queues_and_zones(output_file):
153 | global zones
154 | global printers_ids
155 |
156 | # Reverse the printer_ids dictionary for easy lookup
157 | printer_keys = {v: k for k, v in printers_ids.items()}
158 |
159 | # Track assigned printers
160 | assigned_printers = set()
161 |
162 | # Prepare CSV data
163 | csv_data = [["Zone Name", "Print Queue", "Optional", "Default"]]
164 |
165 | for zone in zones:
166 | zone_name = zone['name']
167 | for pq in zone['zonePrintQueues']:
168 | if pq['key'] in printer_keys:
169 | print_queue = printer_keys[pq['key']]
170 | optional = "True" if pq.get('optional', False) else ""
171 | default = "True" if pq.get('default', False) else ""
172 | csv_data.append([zone_name, print_queue, optional, default])
173 | assigned_printers.add(pq['key'])
174 | if not zone['zonePrintQueues']:
175 | csv_data.append([zone_name, "", "", ""])
176 |
177 | # Add unassigned printers
178 | unassigned_printers = sorted(set(printers_ids.values()) - assigned_printers)
179 | for printer_key in unassigned_printers:
180 | printer_name = printer_keys.get(printer_key, '')
181 | csv_data.append(["", printer_name, "", ""])
182 |
183 | # Write to CSV file
184 | with open(output_file, 'w', newline='') as file:
185 | writer = csv.writer(file)
186 | writer.writerows(csv_data)
187 |
188 | if __name__ == "__main__":
189 |
190 |
191 | # Create the argument parser
192 | parser = argparse.ArgumentParser(description='Create zones based on CSV.')
193 |
194 | # Add input arguments
195 | parser.add_argument('-p', '--password', type=str, help='Password for authentication')
196 | parser.add_argument('-u', '--username', type=str, help='Username for authentication')
197 | parser.add_argument('-P', '--port', type=int, help='Port number for the connection')
198 | parser.add_argument('--host', type=str, help='Port number for the connection')
199 | parser.add_argument('-f','--file', type=str, help='The CSV input file to process.')
200 | parser.add_argument('--output', type=str, help='Download CSV file with current print queue assignment. Useful to edit and import again.')
201 | parser.add_argument('--printer', help='If --printer argument is present, a single printer will be assigned to to the zone specified by --zone. ')
202 | parser.add_argument('-z','--zone', help='The zone the single printer (--printer) will be connected to.')
203 | parser.add_argument('-o','--optional',action='store_true',help='If set, then the single printer (--printer) will be connected to the zone (--zone) as optional. ')
204 | parser.add_argument('-d','--default',action='store_true',help='If set, then this printer will be set to the default printer of the zone, and any previously default printers will be cleared.')
205 | parser.add_argument('-e','--edit',action='store_true',help='If set, then the optional and default flag will be updated if a print queue is already deployed to a zone.')
206 |
207 | # Parse the command line arguments
208 | args = parser.parse_args()
209 |
210 | #Default password to "password" if not set
211 | if args.password:
212 | password = args.password
213 | else:
214 | print("Password not set. Using 'password' as default password. Set password with --password input argument.")
215 |
216 | #Default username to "admin" if not set
217 | if args.username:
218 | username = args.username
219 | else:
220 | print("Username not set. Using 'admin' as default username. Set username with --username input argument.")
221 |
222 | #Default host to "localhost" if not set
223 | if args.host:
224 | host_name = args.host
225 | else:
226 | print("Host not set. Using 'localhost' as default host. Set host with --host input argument.'")
227 |
228 | #Default port to "9192" if not set
229 | if args.port:
230 | port = args.port
231 | else:
232 | print("Port not set. Using 9192 as default port. Set port with --port input argument.'")
233 |
234 | #Setting optional variable. This won't be used for a CSV import, only when one specific print queue is imported.
235 | optional = args.optional
236 |
237 | #Setting default variable. This won't be used for a CSV import, only when one specific print queue is imported.
238 | default = args.default
239 |
240 | #If set, then the optional and default flag will be updated if a print queue is already deployed to a zone.
241 | edit = args.edit
242 |
243 | zone_id = None
244 | printers_ids = {}
245 | zone_print_queues = []
246 |
247 |
248 | if not login(username,password):
249 | print("Exiting: Couldn't login")
250 | sys.exit(1)
251 | #############
252 |
253 | #Retrieve zones and connected print queues
254 | zones = get_data("/print-deploy/admin/api/zones")
255 |
256 | #Retrieve all printer IDS
257 | printers = get_data("/print-deploy/admin/api/printQueues")
258 | for i in printers:
259 | printers_ids[i["name"]]=i["key"]
260 |
261 | #Export current config
262 | if args.output:
263 | export_print_queues_and_zones(args.output)
264 |
265 | #If print queue name and zone are provided through arguments
266 | if args.printer and args.zone:
267 | new_print_queue_name = args.printer
268 | zone_name = args.zone
269 | connect_print_queue_to_zone(zone_name,new_print_queue_name,optional,default,edit)
270 |
271 | #If CSV file provided
272 | if args.file:
273 | csv_file_path = args.file
274 | print(f"Processing file '{csv_file_path}'")
275 | # Read data from the CSV file
276 | with open(csv_file_path, mode='r') as file:
277 | reader = csv.DictReader(file)
278 | for row in reader:
279 | zone_name = row['Zone Name'].strip()
280 | new_print_queue_name = row['Print Queue'].strip()
281 | optional = row['Optional'].strip().lower() == 'true'
282 | default = row['Default'].strip().lower() == 'true'
283 | #Check if zone name and print queue name not empty.
284 | if zone_name and new_print_queue_name:
285 | connect_print_queue_to_zone(zone_name,new_print_queue_name,optional,default,edit)
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
--------------------------------------------------------------------------------
/print-deploy-bulk-scripts/create_zones.py:
--------------------------------------------------------------------------------
1 | #
2 | # (c) Copyright 1999-2024 PaperCut Software International Pty Ltd.
3 | #
4 |
5 | import requests
6 | import csv
7 | import argparse
8 | import json
9 | import sys
10 |
11 | # Suppress warnings for self-signed certificates. Remove this code in environments where CA-signed certificates are used.
12 | import urllib3
13 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
14 |
15 |
16 | #The script will default to these values are not specified. Don't edit these in the script, rather provide them through input arguments. Run "python 3 create_zones.py --help"
17 | host_name = 'localhost'
18 | port = 9192
19 | password = "password"
20 | username = "admin"
21 |
22 | #Global variables
23 | session = None
24 |
25 | def login(username,password):
26 | #Get SessionID
27 | global session
28 | session = requests.Session()
29 | # First request to get the session cookies
30 | response = session.get("https://{}:{}/admin".format(host_name,port), verify=False)
31 |
32 | # Define the URL
33 | url = 'https://{}:{}/app'.format(host_name,port)
34 |
35 | headers = {
36 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
37 | "Content-Type": "application/x-www-form-urlencoded",
38 | "Host": "{}:{}".format(host_name,port),
39 | "Referer": "https://{}:{}/admin".format(host_name,port),
40 | }
41 |
42 |
43 | # Define the request body
44 | body = {
45 | "service": "direct/1/Home/$Form",
46 | "sp": "S0",
47 | "Form0": "$Hidden$0,$Hidden$1,inputUsername,inputPassword,$Submit$0,$PropertySelection",
48 | "$Hidden$0": "true",
49 | "$Hidden$1": "X",
50 | "inputUsername": username,
51 | "inputPassword": password,
52 | "$Submit$0": "Log in",
53 | "$PropertySelection": "en"
54 | }
55 |
56 | response = session.post(url, headers=headers, data=body, verify=False)
57 |
58 | # Check the response
59 | if response.status_code == 200 or response.status_code == 302:
60 | # Check if the response contains the string "Login"
61 | if "Login" in response.text:
62 | print("Username or password not correct")
63 | return False
64 | else:
65 | print("Login was successful")
66 | return True
67 | else:
68 | print("Request failed with status code %s" % response.status_code)
69 | return False
70 |
71 | def get_data(path):
72 |
73 | #############
74 |
75 | # Define the URL
76 | url = 'https://{}:{}{}'.format(host_name,port,path)
77 |
78 | # Define the headers
79 | headers = {'Content-Type': 'application/json'}
80 |
81 | # Send the POST request
82 | response = session.get(url, headers=headers, verify=False)
83 | # Check the response
84 | if response.status_code == 200:
85 | return response.json()
86 | else:
87 | print("Request to '{}' failed with status code {}".format(path,response.status_code))
88 | return False
89 |
90 | def post_data(path,data):
91 | #############
92 |
93 | # Define the URL
94 | url = 'https://{}:{}{}'.format(host_name,port,path)
95 |
96 | # Define the headers
97 | headers = {'Content-Type': 'application/json'}
98 |
99 | # Send the POST request
100 | response = session.post(url, headers=headers, data=json.dumps(data), verify=False)
101 | if response.status_code == 200 or response.status_code == 302 or response.status_code == 201:
102 | return True
103 | else:
104 | print("Request to '{}' failed with status code {}".format(path,response.status_code))
105 | return False
106 |
107 | def read_list_from_string(input_string):
108 | #Check if empty string or only spaces
109 | if input_string.strip() == '':
110 | return []
111 | # Check if a comma exists in the input string
112 | if ',' in input_string:
113 | # Split the string on commas and return the list of strings
114 | return input_string.split(',')
115 | else:
116 | # If no comma exists, return the original string in a list
117 | return [input_string]
118 |
119 |
120 | if __name__ == "__main__":
121 |
122 | # Create the argument parser
123 | parser = argparse.ArgumentParser(description='Create zones based on CSV.')
124 |
125 | # Add input arguments
126 | parser.add_argument('-e', '--edit', action='store_true', help='If -e or --edit argument is present, then zones will be updated with the CSV configuration. Otherwise the existing zones will be skipped.')
127 | parser.add_argument('-p', '--password', type=str, help='Password for authentication')
128 | parser.add_argument('-u', '--username', type=str, help='Username for authentication')
129 | parser.add_argument('-P', '--port', type=int, help='Port number for the connection')
130 | parser.add_argument('--host', type=str, help='Port number for the connection')
131 |
132 | # Add required positional arguments
133 | parser.add_argument('csv_file_path', type=str, help='The CSV input file to process')
134 |
135 | # Parse the command line arguments
136 | args = parser.parse_args()
137 |
138 | # Check that we at least have a CSV filename.
139 | if args.csv_file_path:
140 | print(f"Input CSV file provided: {args.csv_file_path}")
141 |
142 | else:
143 | print("Required argument 'csv_file_path' was not provided. At a minimum, run 'python3 zones.csv' if your file is zones.csv.")
144 | sys.exit(1)
145 |
146 | #Default password to "password" if not set
147 | if args.password:
148 | password = args.password
149 | else:
150 | print("Password not set. Using 'password' as default password. Set password with --password input argument.")
151 |
152 | #Default username to "admin" if not set
153 | if args.username:
154 | username = args.username
155 | else:
156 | print("Username not set. Using 'admin' as default username. Set username with --username input argument.")
157 |
158 | #Default host to "localhost" if not set
159 | if args.host:
160 | host_name = args.host
161 | else:
162 | print("Host not set. Using 'localhost' as default host. Set host with --host input argument.'")
163 |
164 | #Default port to "9192" if not set
165 | if args.port:
166 | port = port
167 | else:
168 | print("Port not set. Using 9192 as default port. Set port with --port input argument.'")
169 |
170 | if not login(username,password):
171 | print("Exiting: Couldn't login")
172 | sys.exit(1)
173 | #############
174 |
175 | #Retrieve zones
176 | existing_zones = get_data("/print-deploy/admin/api/zones")
177 |
178 | # Read data from the CSV file
179 | with open(args.csv_file_path, mode='r') as file:
180 | reader = csv.DictReader(file)
181 | for row in reader:
182 | zonename = row['Zone Name']
183 | displayname = row['Display Name']
184 | groups = read_list_from_string(row['Groups'])
185 | ipRange = read_list_from_string(row['IP Range'])
186 | ipRange = ["0.0.0.0-255.255.255.255"] if not ipRange else ipRange #If ipRanga is empty, replace it with ["0.0.0.0-255.255.255.255"]
187 | hostnameRegex = row['Hostname Regex']
188 | hostnameRegex = None if not hostnameRegex.strip() else hostnameRegex #Value of null will be sent via JSON if cell is empty
189 | #Prep data that will be posted to Print Deploy to create or edit a zone
190 | data = {
191 | 'groups': groups,
192 | 'ipRange': ipRange,
193 | 'name': zonename,
194 | 'displayName': displayname,
195 | 'hostnameRegex': hostnameRegex
196 |
197 | }
198 |
199 | zone_already_exists = any(zone.get('name') == zonename for zone in existing_zones)
200 | if zone_already_exists and args.edit:
201 | print(f"Editing '{zonename}' as it already exists and '-e' or '--edit' argument was set. ")
202 | #Get existing zone ID for zone with matching name
203 | matching_zones = filter(lambda d: 'name' in d and d['name'] == zonename, existing_zones)
204 | zone_id = (next(matching_zones, None)).get('id')
205 |
206 | # Send the POST request
207 | post_data("/print-deploy/admin/api/zones/{}".format(zone_id),data)
208 | #Skipping zone
209 | elif zone_already_exists:
210 | print(f"Skipping '{zonename}' as it already exists. If you want to edit the zone instead, run the script with '-e' argument.")
211 | #Create new zone
212 | else:
213 | print(f"Creating zone '{zonename}'.")
214 |
215 | # Send the POST request
216 | post_data("/print-deploy/admin/api/zones",data)
217 |
218 |
--------------------------------------------------------------------------------
/print-deploy-bulk-scripts/printers_to_zones_example.csv:
--------------------------------------------------------------------------------
1 | Zone Name,Print Queue,Optional,Default
2 | Zone2,Printer_1,True,
--------------------------------------------------------------------------------
/print-deploy-bulk-scripts/zones.csv:
--------------------------------------------------------------------------------
1 | Zone Name,Display Name,Groups,IP Range,Hostname Regex
2 | Zone1,test,"staff,papercut",,(?i)frontdesk
3 | Zone2,,,192.168.2.1-192.168.2.10,
4 | Zone3,Display3,,"192.168.1.1-192.168.1.10,192.168.0.0/24",
5 | Zone6,Display4,staff,,
--------------------------------------------------------------------------------