├── 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 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
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 |

37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
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 |
6 | %for row in rows: 7 | {{row}}
8 | %end 9 | 10 | 11 | 12 |
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,, --------------------------------------------------------------------------------