├── FFS ├── ffs_example.exe └── ffs_example.ps1 ├── AccessLockUtility ├── access_lock.exe ├── access_lock.pdf └── access_lock.ps1 ├── CompiledScripts ├── licenseAvailabilityReport_2_0_1.exe └── README.md ├── c42SharedLibScripts ├── README.md ├── legacy_pre_4.2 │ ├── orgDevices.py │ ├── sharedLibTest.py │ ├── deviceStats.py │ ├── upgradeDevicesByOrg.py │ ├── moveUsersByOrg.py │ ├── getInfoByDeviceGUID.py │ ├── pushRestore.py │ ├── allDeviceReport.py │ ├── ExportAllUserDataCreationDate.py │ └── allUserAndDeviceReport.py └── v1.55 │ └── UserQuotaReport.py ├── AuthorityMigrationHelpers ├── README.md ├── usernameToEmails.py └── cloudRoleAdder.py ├── addLDAPUsers_ReadMe ├── NoBackupsInXDays.ps1 ├── addLDAPUsers.py ├── README.md ├── SearchChecksum.sh ├── pushRestore.sh ├── deactivateDevices.py └── restoreWatch.py /FFS/ffs_example.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code42/crashplan_api_examples/HEAD/FFS/ffs_example.exe -------------------------------------------------------------------------------- /AccessLockUtility/access_lock.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code42/crashplan_api_examples/HEAD/AccessLockUtility/access_lock.exe -------------------------------------------------------------------------------- /AccessLockUtility/access_lock.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code42/crashplan_api_examples/HEAD/AccessLockUtility/access_lock.pdf -------------------------------------------------------------------------------- /CompiledScripts/licenseAvailabilityReport_2_0_1.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code42/crashplan_api_examples/HEAD/CompiledScripts/licenseAvailabilityReport_2_0_1.exe -------------------------------------------------------------------------------- /c42SharedLibScripts/README.md: -------------------------------------------------------------------------------- 1 | # Code42 Shared Python Library 2 | This library is written in Python 2.7 in order to make common operations easier, such as getting authentication tokens, retrieving all users in the system, or getting all devices. 3 | 4 | ## v1.7 5 | This is the most current version of the shared library. 6 | 7 | ## v1.55 8 | While not the most current, there are still scripts that some may find useful, and may need minor modifications to get working with the 1.7 shared library. 9 | 10 | ## legacy_pre_4.2 11 | This folder contains scripts and the shared library version that works on Code42 on-premesis server versions prior to 4.2. The scripts have not been tested on later server versions and may need to be updated to use later versions of the shared library. 12 | -------------------------------------------------------------------------------- /AuthorityMigrationHelpers/README.md: -------------------------------------------------------------------------------- 1 | # Authority Migration Helpers 2 | These scripts are designed for customers that will be migrating their on-prem Code42 authority into the Code42 multi-tenant cloud. Two of the most common tasks that admins need to do are to remove any custom roles, and to make sure all of their usernames are in an e-mail format. 3 | For both scripts run with -h to get help on the syntax needed for running the scripts. 4 | 5 | Required non default libraries: 6 | requests 7 | pandas 8 | 9 | 10 | ### roleChanger.py 11 | This is a standalone Python script which will walk the admin through mass role changes in their environment to get rid of custom roles, and to add roles that can be translated into the cloud easier than going one-by-one in the web interface. 12 | 13 | ### usernamesToEmails.py 14 | This is a standalone Python script which will change users' usernames to the e-mail format required by the Code42 cloud based on one of three methods that the admin can specify: 15 | 1. Make user's email their username 16 | * This method simply copies whatever is used in the Code42 `email` field and pastes it into their `username` field 17 | * For the example user below, the username would get changed to ` bob@company.com` 18 | 2. Append a static domain to the username 19 | * This method takes the current username and appends an admin-defined suffix (` @company.com`) at the end 20 | * For the example user below, the username would get changed to ` bsmith@company.com` 21 | 3. Use the user's first and last name, along with a static domain 22 | * This method uses the `First Name` and `Last Name` fields of a Code42 user account as well as an admin-defined domain suffix and puts the result in the username field. 23 | * For the example user below, the username would get changed to ` bob.smith@company.com` 24 | 25 | Example User (beginning state): 26 | * username: `bsmith` 27 | * email: ` bob@company.com` 28 | * First Name: `Bob` 29 | * Last Name: `Smith` 30 | -------------------------------------------------------------------------------- /CompiledScripts/README.md: -------------------------------------------------------------------------------- 1 | # Code42 Compiled Scripts 2 | Here you will find scripts you can download and use right away, they have all been compiled from their like-named python source in other folders. 3 | 4 | ### licenseAvailabilityReport_2_0_1.exe 5 | Creates a list of users whose license usage will expire at a future date 6 | 7 | Usage: 8 | licenseAvailabilityReport.py [-l] [(u ) | (c )] [(s ) | m ( )] [--filePath=] [--orgId=] [--logLevel=] 9 | 10 | Arguments: 11 | Add your username - you will be prompted for your password 12 | Enter a base64 encoded creditials file location 13 | File to grab the host name & port 14 | Server URL 15 | Server Port 16 | File Path for log and CSV files. Must exist before running script. 17 | Logging Level - defaults to INFO. Values are INFO, DEBUG and ERROR 18 | u flag to enter username then be prompted for password 19 | c flag to enter the name of a credentials file. 20 | s flag to read server info from a file 21 | m flag to manually enter server URL and Port 22 | 23 | 24 | Options: 25 | 26 | -f, --filePath= File Path for log and CSV files - optional. [default: ''] 27 | -o, --orgId= Limit list to this comma separated list of orgs (no spaces) 28 | -e Execute mode - otherwise defaults to test mode. 29 | -q Quit processing after the days connected has been reached 30 | -l Turn off logging to console 31 | -h, --help Show this screen. 32 | --version Show version. 33 | -------------------------------------------------------------------------------- /addLDAPUsers_ReadMe: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Code42, Inc. 2 | # Permission is hereby granted, free of charge, to any person obtaining a copy 3 | # of this software and associated documentation files (the "Software"), to deal 4 | # in the Software without restriction, including without limitation the rights 5 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 6 | # copies of the Software, and to permit persons to whom the Software is 7 | # furnished to do so, subject to the following conditions: 8 | # The above copyright notice and this permission notice shall be included in all 9 | # copies or substantial portions of the Software. 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 11 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 12 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 13 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 14 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 15 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 16 | # SOFTWARE. 17 | File: addLDAPUsers.py ReadMe 18 | Author: Nick Olmsted, Code 42 Software 19 | Last Modified: 04-22-2013 20 | 21 | SUMMARY: 22 | Takes a comma-delimited CSV file of user names and adds those users to a LDAP Org. 23 | 24 | PRE-REQS: 25 | * Python 2.7 26 | * Requests Module 27 | ** http://www.python-requests.org/ 28 | * PROe Server Host and Port 29 | * PROe Server Admin and Password 30 | * CSV file name 31 | * LDAP ORG ID. You can get this by navigating to the org and getting the org id value from the url. For examples: http://localhost:4280/console/app.html#v=orgs:overview&t=0mbunsjn5sbz91tlzg5clpd7qg&s=orgDetail&so[orgId]=3 would be an Org ID of 3. 32 | 33 | STEPS: 34 | 1. Create CSV file with comma-delmited list of LDAP users to add to the PROe Server. Save it in the same location as the addLDAPUsers.py script. 35 | 2. Update addLDAPUsers.py and add your environment values for cp_host, cp_port, etc. 36 | 3. Execute the script and check the addLDAPUsers.log file for your results. 37 | 38 | RESULTS: 39 | LDAP Users will be added to the specified CrashPlan Organization. 40 | -------------------------------------------------------------------------------- /c42SharedLibScripts/legacy_pre_4.2/orgDevices.py: -------------------------------------------------------------------------------- 1 | # By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no cost separately from Code42's commercial offerings. 2 | # This software is not provided under Code42's master services agreement. 3 | # It is provided AS-IS, without support, and subject to the license below. 4 | # Any support and documentation for this software are available at the Code42 community site. 5 | 6 | # The MIT License (MIT) 7 | # Copyright (c) 2019 Code42 8 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 10 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 11 | # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 12 | # is furnished to do so, subject to the following conditions: 13 | 14 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 17 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | # 22 | # File: getInfoByDeviceGUID.py 23 | # Author: AJ LaVenture, Code 42 Software 24 | # Last Modified: 08-23-2013 25 | # 26 | # From a list of Device GUIDS, pull device and user information into CSV Format 27 | # 28 | 29 | from c42SharedLibrary import c42Lib 30 | import math 31 | import sys 32 | import json 33 | import csv 34 | import base64 35 | import logging 36 | import requests 37 | import math 38 | from dateutil.relativedelta import * 39 | import datetime 40 | import calendar 41 | 42 | 43 | #Test values 44 | c42Lib.cp_host = "http://localhost" 45 | c42Lib.cp_port = "4280" 46 | c42Lib.cp_username = "admin" 47 | c42Lib.cp_password = "admin" 48 | 49 | 50 | c42Lib.cp_logLevel = "INFO" 51 | c42Lib.cp_logFileName = "getInfoByDeviceGUID.log" 52 | c42Lib.setLoggingLevel() 53 | 54 | csvFile = csv.writer(open("orgDevices.csv", "wb+")) 55 | csvHeaders = ['computerId','computerName', 'userId', 'username', 'email', 'firstName', 'lastName'] 56 | 57 | csvFile.writerow(csvHeaders) 58 | print csvHeaders 59 | 60 | orgId = 3 61 | computers = c42Lib.getAllDevicesByOrg(orgId) 62 | 63 | 64 | for index, computer in enumerate(computers): 65 | printRow = [] 66 | 67 | computerId = computer['computerId'] 68 | printRow.extend([str(computerId)]) 69 | 70 | computerName = computer['name'] 71 | printRow.extend([computerName.encode('utf-8')]) 72 | 73 | userId = computer['userId'] 74 | printRow.extend([str(userId)]) 75 | 76 | userObject = c42Lib.getUserById(userId) 77 | username = userObject['username'] 78 | printRow.extend([username.encode('utf-8')]) 79 | 80 | 81 | email = userObject['email'] 82 | if email is None: 83 | printRow.extend([""]) 84 | else: 85 | printRow.extend([email.encode('utf-8')]) 86 | 87 | firstName = userObject['firstName'] 88 | if firstName is None: 89 | printRow.extend([""]) 90 | else: 91 | printRow.extend([firstName.encode('utf-8')]) 92 | 93 | lastName = userObject['lastName'] 94 | if lastName is None: 95 | printRow.extend([""]) 96 | else: 97 | printRow.extend([lastName.encode('utf-8')]) 98 | 99 | 100 | csvFile.writerow(printRow) 101 | print printRow 102 | 103 | 104 | -------------------------------------------------------------------------------- /c42SharedLibScripts/legacy_pre_4.2/sharedLibTest.py: -------------------------------------------------------------------------------- 1 | # By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no cost separately from Code42's commercial offerings. 2 | # This software is not provided under Code42's master services agreement. 3 | # It is provided AS-IS, without support, and subject to the license below. 4 | # Any support and documentation for this software are available at the Code42 community site. 5 | 6 | # The MIT License (MIT) 7 | # Copyright (c) 2019 Code42 8 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 10 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 11 | # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 12 | # is furnished to do so, subject to the following conditions: 13 | 14 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 17 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | from c42SharedLibrary import c42Lib 22 | import math 23 | import sys 24 | import json 25 | import csv 26 | import base64 27 | import logging 28 | import requests 29 | import math 30 | from dateutil.relativedelta import * 31 | import datetime 32 | import calendar 33 | import getpass 34 | 35 | # Test values 36 | c42Lib.cp_host = "http://localhost" 37 | c42Lib.cp_port = "4280" 38 | c42Lib.cp_username = "admin" 39 | # c42Lib.cp_password = "admin" 40 | c42Lib.cp_password = getpass.getpass('Enter your CrashPlan console password: ') # You will be prompted for your password 41 | c42Lib.cp_logLevel = "DEBUG" 42 | c42Lib.cp_logFileName = "sharedLibTest.log" 43 | c42Lib.setLoggingLevel() 44 | 45 | 46 | # users = c42Lib.generaticLoopUntilEmpty() 47 | # servers = c42Lib.getServersByDestinationId(2) 48 | # restoreList = c42Lib.getRestoreHistoryForOrgId(36) 49 | # restoreList = c42Lib.getRestoreHistoryForUserId(3) 50 | restoreList = c42Lib.getRestoreHistoryForComputerId(11) 51 | 52 | print restoreList 53 | # payload = {'orgId': '0', 'pgNum': str(1), 'pgSize': str(c42Lib.MAX_PAGE_NUM)} 54 | 55 | # r = c42Lib.executeRequest("get", c42Lib.cp_api_user, payload) 56 | 57 | # logging.debug(r.text) 58 | 59 | # content = r.content 60 | # binary = json.loads(content) 61 | # logging.debug(binary) 62 | 63 | # users = binary['data'] 64 | # print users 65 | 66 | # print c42Lib.getAllUsers() 67 | # c42Lib.getDevicesPageCountByOrg(3) 68 | # users = c42Lib.getAllUsersByOrg(3) 69 | 70 | # http://aj-proappliance:4280/api/User?orgId=3&pgNum=1&pgSize=250&active=true 71 | # orgId = "35" 72 | # pgNum = 1 73 | 74 | 75 | # header = c42Lib.getRequestHeaders() 76 | # url = c42Lib.getRequestUrl(c42Lib.cp_api_user) 77 | # payload = {'orgId': orgId, 'pgNum': str(pgNum), 'pgSize': str(c42Lib.MAX_PAGE_NUM), 'active': 'true'} 78 | # logging.info(str(payload)) 79 | # r = requests.get(url, params=payload, headers=header) 80 | # content = r.content 81 | # binary = json.loads(content) 82 | # logging.debug(binary) 83 | 84 | 85 | # users = binary['data']['users'] 86 | 87 | # users = c42Lib.getUsersByOrgPaged(35, 1) 88 | # for user in users: 89 | # userId = user["userId"] 90 | # print "--------------" 91 | # print userId 92 | 93 | # c42Lib.getArchivesPageCount('serverId',3) 94 | # c42Lib.getArchiveByServerId(3) 95 | -------------------------------------------------------------------------------- /c42SharedLibScripts/legacy_pre_4.2/deviceStats.py: -------------------------------------------------------------------------------- 1 | # By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no cost separately from Code42's commercial offerings. 2 | # This software is not provided under Code42's master services agreement. 3 | # It is provided AS-IS, without support, and subject to the license below. 4 | # Any support and documentation for this software are available at the Code42 community site. 5 | 6 | # The MIT License (MIT) 7 | # Copyright (c) 2019 Code42 8 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 10 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 11 | # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 12 | # is furnished to do so, subject to the following conditions: 13 | 14 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 17 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | # 22 | # File: deviceStats.py 23 | # Author: AJ LaVenture, Code 42 Software 24 | # Last Modified: 10-21-2014 25 | # 26 | # Calculate total processed data by clients and compare to total storage numbers 27 | # 28 | # Assumptions: 29 | # all devices are pulling just the first destination 30 | # storedBytesHistory is all desitnations 31 | # therefore numbers assumed all devices only have 1 destination, therefore sum of devices bytes will be 32 | # represented - percentage will drop due to increased versions and deleted files 33 | 34 | # api/computer/xxxx?incBackupUsage=true 35 | # data.backupUsage[0].selectedBytes 36 | # data.backupUsage[0].percentComplete 37 | 38 | # api/storedBytesHistory 39 | 40 | 41 | from c42SharedLibrary import c42Lib 42 | import logging 43 | import getpass 44 | import json 45 | 46 | c42Lib.cp_host = "http://aj-ubuntu" 47 | c42Lib.cp_port = "4280" 48 | 49 | c42Lib.cp_username = "admin" 50 | c42Lib.cp_password = getpass.getpass() 51 | c42Lib.cp_logLevel = "INFO" 52 | c42Lib.cp_logFileName = "deviceStats.log" 53 | c42Lib.setLoggingLevel() 54 | 55 | 56 | def getStoredBytesHistory(): 57 | params = {} 58 | payload = {} 59 | 60 | r = c42Lib.executeRequest("get", "/api/storedBytesHistory", params, payload) 61 | 62 | content = r.content 63 | binary = json.loads(content) 64 | 65 | latestStoredNumber = binary['data'][0][1]['archiveBytes'] 66 | 67 | return latestStoredNumber 68 | 69 | 70 | 71 | devices = c42Lib.getAllDevices() 72 | 73 | totalDestinationData = getStoredBytesHistory() 74 | 75 | totalDeviceData = 0 76 | 77 | for index, device in enumerate(devices): 78 | cur_selectedBytes = 0 79 | cur_percentComplete = 0 80 | 81 | if device['backupUsage']: 82 | cur_selectedBytes = device['backupUsage'][0]['selectedBytes'] 83 | # print str(cur_selectedBytes) 84 | cur_percentComplete = int(device['backupUsage'][0]['percentComplete']) 85 | # print str(cur_percentComplete) 86 | 87 | totalDeviceData = totalDeviceData + (cur_selectedBytes * (float(cur_percentComplete) / 100)) 88 | 89 | finalRatio = (1 - (float(totalDestinationData)/totalDeviceData)) * 100 90 | print "totalDeviceData: ", '%f' % totalDeviceData, c42Lib.sizeof_fmt_si(totalDeviceData) 91 | print "totalDestinationData: ", '%f' % totalDestinationData, c42Lib.sizeof_fmt_si(totalDestinationData) 92 | print "total device to destination ratio %: ", finalRatio 93 | -------------------------------------------------------------------------------- /NoBackupsInXDays.ps1: -------------------------------------------------------------------------------- 1 | # By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no cost separately from Code42's commercial offerings. 2 | # This software is not provided under Code42's master services agreement. 3 | # It is provided AS-IS, without support, and subject to the license below. 4 | # Any support and documentation for this software are available at the Code42 community site. 5 | 6 | # The MIT License (MIT) 7 | # Copyright (c) 2019 Code42 8 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 10 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 11 | # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 12 | # is furnished to do so, subject to the following conditions: 13 | 14 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 17 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | # 22 | # Description: This script will look for devices that have not completed a backup 23 | # in the last X (Specified while running) days, but do NOT have a backup alert 24 | # (Meaning they have some backup activity recently). It will output a CSV file 25 | # with the username, device name, destination name, last activity date and the 26 | # last completed backup date. 27 | # 28 | # Authors: Mike Hamilton, Mike Wallace 29 | # 30 | ################################### 31 | # Variables (Customize) 32 | ################################### 33 | 34 | # Your master server address and port number 35 | $MASTER="http://crashplan.demo42.com:4280" 36 | # If you want to hardcode number of days, set this variable to the number of days 37 | $days = Read-Host -Prompt 'Enter number of days without complete backup' 38 | 39 | ################################### 40 | # Other Variables 41 | ################################### 42 | $uriC = $MASTER + "/api/DeviceBackupReport?&active=true&alerted=false&srtKey=lastCompletedBackupDate" 43 | $cred = get-credential 44 | $a = Invoke-restmethod -Credential $cred -URi $uriC -Method Get 45 | $compdate = (get-date).adddays(-$days) 46 | $csv = "username","deviceName","destinationName","lastActivity","lastCompletedBackupDate" 47 | $csv -join "," >> "NoCompletedBackups${days}Days.csv" 48 | 49 | # Loop through each entry in device backup report 50 | foreach ($d in $a.data) { 51 | 52 | # If a backup has never been completed, export that device 53 | if (!$d.lastCompletedBackupDate){ 54 | $outp=$d.username + "," + $d.deviceName + "," + $d.destinationName + "," + $d.lastActivity + "," + "Never Completed" 55 | $outp >> "NoCompletedBackups${days}Days.csv" 56 | } 57 | 58 | # If a backup has completed, perform date conversions 59 | else{ 60 | $pdate=$d.lastCompletedBackupDate.split("T") 61 | $pdate=get-date($pdate[0]) 62 | $pudate=(get-date $pdate).date 63 | $cudate=(get-date $compdate).date 64 | 65 | # Export active devices that have not completed a backup within X days 66 | if ((get-date $pdate).date -lt (get-date $compdate).date) { 67 | $outp=$d.username + "," + $d.deviceName + "," + $d.destinationName + "," + $d.lastActivity + "," + $d.lastCompletedBackupDate 68 | $outp >> "NoCompletedBackups${days}Days.csv" 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /c42SharedLibScripts/legacy_pre_4.2/upgradeDevicesByOrg.py: -------------------------------------------------------------------------------- 1 | # By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no cost separately from Code42's commercial offerings. 2 | # This software is not provided under Code42's master services agreement. 3 | # It is provided AS-IS, without support, and subject to the license below. 4 | # Any support and documentation for this software are available at the Code42 community site. 5 | 6 | # The MIT License (MIT) 7 | # Copyright (c) 2019 Code42 8 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 10 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 11 | # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 12 | # is furnished to do so, subject to the following conditions: 13 | 14 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 17 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | # 22 | # File: upgradeDevicesByOrg.py 23 | # Author: AJ LaVenture, Code 42 Software 24 | # Last Modified: 2-10-2014 25 | # 26 | # Specify organizaions and flag all devices to upgrade. 27 | # 28 | """upgradeDevicesByOrg Script 29 | 30 | Usage: 31 | upgradeDevicesByOrg.py 32 | 33 | 34 | Arguments: 35 | required id of the organizaion to send the upgrade to devices: 0 for all devices regardless of org. 36 | 37 | Options: 38 | -h --help Show this screen. 39 | --version Show version. 40 | 41 | """ 42 | from docopt import docopt 43 | from c42SharedLibrary import c42Lib 44 | import json 45 | import logging 46 | import requests 47 | import getpass 48 | 49 | 50 | if __name__ == '__main__': 51 | arguments = docopt(__doc__, version='upgradeDevicesByOrg 1.0') 52 | print(arguments) 53 | 54 | 55 | c42Lib.cp_host = "http://aj-proappliance" 56 | c42Lib.cp_port = "4280" 57 | c42Lib.cp_username = "admin" 58 | c42Lib.cp_password = getpass.getpass('Enter your CrashPlan console password: ') # You will be prompted for your password 59 | 60 | c42Lib.cp_logLevel = "INFO" 61 | c42Lib.cp_logFileName = "upgradeDevicesByOrg.log" 62 | c42Lib.setLoggingLevel() 63 | 64 | 65 | # ARG1 - Org ID 66 | # cp_orgId = "" 67 | # if len(sys.argv) > 1: 68 | # cp_orgId = str(sys.argv[1]) 69 | 70 | 71 | def performDeviceUpgradeByOrg(orgId): 72 | devices = "" 73 | if (orgId == "0"): 74 | #get devices for all orgs 75 | devices = c42Lib.getAllDevices() 76 | else: 77 | #run get devices 78 | devices = c42Lib.getAllDevicesByOrg(int(orgId)) 79 | if (devices != "" and devices is not None): 80 | for index, device in enumerate(devices): 81 | #update device 82 | computerId = device['computerId'] 83 | flaggedForUpgrade = c42Lib.putDeviceUpgrade(computerId) 84 | if flaggedForUpgrade: 85 | logging.info("computerId:[" + str(computerId) + "] flagged for upgrade") 86 | else: 87 | logging.info("computerId:[" + str(computerId) + "] failed to be flagged for upgrade") 88 | else: 89 | logging.info("No computers returned to flag for upgrade") 90 | 91 | # get devices by org 92 | # perform upgrade 93 | 94 | 95 | # get all orgs 96 | # get devices by org 97 | # perform upgrade 98 | 99 | 100 | 101 | def interpretParamsAndExecute(): 102 | logging.info("upgradeDevicesByOrg Action") 103 | cp_orgId = arguments[''] 104 | if (cp_orgId is not None and cp_orgId != ""): 105 | orgToLog = cp_orgId 106 | if (orgToLog == "0"): 107 | orgToLog = "All Orgs" 108 | logging.info("Upgrading devices for OrgId: " + str(orgToLog)) 109 | performDeviceUpgradeByOrg(cp_orgId) 110 | else: 111 | logging.error("Invalid first argument value. Please enter the orgId for Devices you wish to upgrade.") 112 | 113 | interpretParamsAndExecute() 114 | -------------------------------------------------------------------------------- /c42SharedLibScripts/legacy_pre_4.2/moveUsersByOrg.py: -------------------------------------------------------------------------------- 1 | # By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no cost separately from Code42's commercial offerings. 2 | # This software is not provided under Code42's master services agreement. 3 | # It is provided AS-IS, without support, and subject to the license below. 4 | # Any support and documentation for this software are available at the Code42 community site. 5 | 6 | # The MIT License (MIT) 7 | # Copyright (c) 2019 Code42 8 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 10 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 11 | # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 12 | # is furnished to do so, subject to the following conditions: 13 | 14 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 17 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | # File: moveUsersByOrg.py 22 | # Author: AJ LaVenture, Code 42 Software 23 | # Last Modified: 2-10-2014 24 | # 25 | # Specify source and destination organizaions and move all users 26 | # 27 | """moveUsersByOrg Script 28 | 29 | Usage: 30 | moveUsersByOrg.py 31 | 32 | 33 | Arguments: 34 | required id of the organizaion from which all users will be moved FROM. 35 | required id of the organizaion for which all users in the src will be moved TO. 36 | 37 | Options: 38 | -h --help Show this screen. 39 | --version Show version. 40 | 41 | """ 42 | from docopt import docopt 43 | from c42SharedLibrary import c42Lib 44 | import json 45 | import csv 46 | import logging 47 | import requests 48 | import getpass 49 | 50 | 51 | if __name__ == '__main__': 52 | arguments = docopt(__doc__, version='moveUsersByOrg 1.0') 53 | print(arguments) 54 | 55 | 56 | c42Lib.cp_host = "http://aj-proserver" 57 | c42Lib.cp_port = "4280" 58 | c42Lib.cp_username = "admin" 59 | c42Lib.cp_password = getpass.getpass('Enter your CrashPlan console password: ') # You will be prompted for your password 60 | 61 | c42Lib.cp_logLevel = "INFO" 62 | c42Lib.cp_logFileName = "moveUsersByOrg.log" 63 | c42Lib.setLoggingLevel() 64 | 65 | 66 | 67 | # ARG1 - Source Org ID 68 | # ARG2 - Destination Org ID 69 | # cp_src_orgId = "" 70 | # cp_dest_orgId = "" 71 | # if len(sys.argv) == 3: 72 | # cp_src_orgId = str(sys.argv[1]) 73 | # cp_dest_orgId = str(sys.argv[2]) 74 | 75 | def performUserMove(src_orgId, dest_orgId): 76 | logging.info("performUserMove-params:src_orgId["+src_orgId+"],dest_orgId["+dest_orgId+"]") 77 | 78 | users = c42Lib.getAllUsersByOrg(src_orgId) 79 | # logging.info(users) 80 | 81 | for index, user in enumerate(users): 82 | userId = user["userId"] 83 | 84 | userMoved = c42Lib.postUserMoveProcess(userId, dest_orgId) 85 | if (userMoved): 86 | logging.info("user["+str(userId)+"] moved") 87 | else: 88 | logging.info("user["+str(userId)+"] NOT moved - " + str(userMoved)) 89 | 90 | 91 | 92 | def interpretParamsAndExecute(): 93 | logging.info("moveUsersByOrg Action") 94 | 95 | cp_src_orgId = arguments[''] 96 | cp_dest_orgId = arguments[''] 97 | 98 | if (cp_src_orgId is not None and cp_src_orgId != "") and (cp_dest_orgId is not None and cp_dest_orgId != ""): 99 | if (cp_src_orgId != cp_dest_orgId): 100 | logging.info("Source orgId:[" + str(cp_src_orgId) + "]") 101 | logging.info("Destination orgId:[" + str(cp_dest_orgId) + "]") 102 | performUserMove(cp_src_orgId, cp_dest_orgId) 103 | else: 104 | logging.error("Source and Destination orgs cannot be the same value.") 105 | else: 106 | logging.error("Invalid arguments value. Please enter the Source Org Id first and the Destination Org Id second to move users.") 107 | 108 | interpretParamsAndExecute() 109 | -------------------------------------------------------------------------------- /addLDAPUsers.py: -------------------------------------------------------------------------------- 1 | # By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no cost separately from Code42's commercial offerings. 2 | # This software is not provided under Code42's master services agreement. 3 | # It is provided AS-IS, without support, and subject to the license below. 4 | # Any support and documentation for this software are available at the Code42 community site. 5 | 6 | # The MIT License (MIT) 7 | # Copyright (c) 2019 Code42 8 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 10 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 11 | # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 12 | # is furnished to do so, subject to the following conditions: 13 | 14 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 17 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | # File: addLDAPUsers.py 22 | # Author: Nick Olmsted, Code 42 Software 23 | # Last Modified: 03-30-2015 by Todd Ojala. Environment variable names cp_ldap_orgId and cp_csv_file_name changed 24 | # to ldap_orgId and csv_file_name to match the script below. 25 | # Also added note to usage of cp_host, which must include http or https 26 | # 27 | # Takes a comma-delimited CSV file of user names and adds those users to a LDAP Org. 28 | # 29 | # Python 2.7 30 | # REQUIRED MODULE: Requests 31 | # 32 | # API Call: POST api/User 33 | # 34 | # Arguments: orgId, username 35 | # 36 | 37 | import sys 38 | 39 | import json 40 | 41 | import base64 42 | 43 | import logging 44 | 45 | import csv 46 | 47 | import requests 48 | 49 | import getpass 50 | 51 | global filename 52 | 53 | # Set to your environments values 54 | # Note: cp_host must include http or https in front of host name for script to function 55 | cp_host = "http://172.16.233.142" 56 | cp_port = "4280" 57 | cp_username = "admin" 58 | cp_password = getpass.getpass('Enter your CrashPlan console password: ') # You will be prompted to enter your password 59 | 60 | cp_ldap_orgId = "4" 61 | csv_file_name = "input.csv" 62 | cp_api = "/api/user" 63 | 64 | # 65 | # Compute base64 representation of the authentication token. 66 | # 67 | def getAuthHeader(u,p): 68 | 69 | token = base64.b64encode('%s:%s' % (u,p)) 70 | 71 | return "Basic %s" % token 72 | 73 | # 74 | # Sets logger 75 | # 76 | def setLoggingLevel(): 77 | logging.basicConfig(filename='addLDAPUsers.log',level=logging.DEBUG, format='%(asctime)s %(message)s') 78 | 79 | # 80 | # Adds the user to the LDAP Org. Returns true if API call was successful 81 | # 82 | def addLDAPUser(userId, orgId): 83 | headers = {"Authorization":getAuthHeader(cp_username,cp_password)} 84 | url = cp_host + ":" + cp_port + cp_api 85 | payload = {'orgId': orgId, 'username': userId} 86 | logging.debug("adding user: " + userId) 87 | r = requests.post(url, data=json.dumps(payload), headers=headers) 88 | 89 | logging.debug(r.text) 90 | print r.text 91 | 92 | return r.status_code == requests.codes.ok 93 | 94 | # 95 | # Reads CSV file that contains a comma-delimited list of LDAP users and adds the users through the API. 96 | # 97 | def addLDAPUsers(): 98 | logging.debug('BEGIN - addLDAPUsers') 99 | count = 0 100 | try: 101 | with open(csv_file_name, 'rU') as csvfile: 102 | csvreader = csv.reader(csvfile, delimiter=',', quotechar='|') 103 | for row in csvreader: 104 | #print ', '.join(row) 105 | for item in row: 106 | #print item 107 | if (addLDAPUser(item, cp_ldap_orgId)): 108 | count = count + 1 109 | except csv.Error as e: 110 | sys.exit('file %s, line %d: %s' % (filename, csvreader.line_num, e)) 111 | 112 | print "Total Users Added: " + str(count) 113 | logging.debug("Total Users Added: " + str(count)) 114 | logging.debug('END - addLDAPUsers') 115 | 116 | setLoggingLevel() 117 | addLDAPUsers() 118 | -------------------------------------------------------------------------------- /c42SharedLibScripts/v1.55/UserQuotaReport.py: -------------------------------------------------------------------------------- 1 | # By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no cost separately from Code42's commercial offerings. 2 | # This software is not provided under Code42's master services agreement. 3 | # It is provided AS-IS, without support, and subject to the license below. 4 | # Any support and documentation for this software are available at the Code42 community site. 5 | 6 | # The MIT License (MIT) 7 | # Copyright (c) 2019 Code42 8 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 10 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 11 | # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 12 | # is furnished to do so, subject to the following conditions: 13 | 14 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 17 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | # 22 | # File: allUserAndDeviceReport.py 23 | # Author: Jack Phinney, Code 42 Software 24 | # Last Modified: 03-24-2016 25 | # 26 | # Generates a list of all active users, showing total storage, quota, and whether the user is over quota 27 | # 28 | 29 | from c42SharedLibrary import c42Lib 30 | import math 31 | import sys 32 | import json 33 | import csv 34 | import base64 35 | import logging 36 | import requests 37 | import math 38 | from dateutil.relativedelta import * 39 | import datetime 40 | import calendar 41 | import getpass 42 | 43 | c42Lib.cp_host = "https://10.10.44.135" 44 | c42Lib.cp_port = "4285" 45 | c42Lib.cp_username = "admin" 46 | c42Lib.cp_password = getpass.getpass('Enter your CrashPlan console password: ') # You will be prompted for your password 47 | 48 | c42Lib.cp_logLevel = "INFO" 49 | c42Lib.cp_logFileName = "UserQuotaReport.log" 50 | c42Lib.setLoggingLevel() 51 | 52 | # c42Lib.MAX_PAGE_NUM = 5 53 | 54 | # cp_api_storedBytesHistory = "/api/storedBytesHistory" 55 | 56 | 57 | 58 | csvUserHeaders = ['userId','username','orgId','orgName','archiveBytes','quotaInBytes','overQuota'] 59 | 60 | emptyBlock = "" 61 | 62 | printValues = [] 63 | printRow = [] 64 | 65 | def printUserEmptyBlock(): 66 | printRow.extend([emptyBlock]) 67 | # printRow.extend([emptyBlock]) 68 | # printRow.extend([emptyBlock]) 69 | 70 | def getUserReport(): 71 | csvUserFile = csv.writer(open("UserQuotaReport.csv", "wb+")) 72 | csvUserFile.writerow(csvUserHeaders) 73 | 74 | users = c42Lib.getAllUsersActiveBackup() 75 | for index, user in enumerate(users): 76 | 77 | printRow = [] 78 | 79 | pUserId = user['userId'] 80 | printRow.extend([str(pUserId)]) 81 | 82 | pUserName = user['username'] 83 | printRow.extend([pUserName.encode('utf-8')]) 84 | 85 | pUserOrgId = user['orgId'] 86 | printRow.extend([str(pUserOrgId)]) 87 | 88 | pUserOrgName = user['orgName'] 89 | printRow.extend([pUserOrgName.encode('utf-8')]) 90 | 91 | userBackupArchiveBytes = 0 92 | 93 | overQuota = False 94 | 95 | if 'backupUsage' in user: 96 | 97 | userBackupUsage = user['backupUsage'] 98 | 99 | # print userBackupUsage 100 | # raw_input() 101 | 102 | if userBackupUsage: 103 | 104 | if 'archiveBytes' in userBackupUsage[0]: 105 | 106 | userBackupArchiveBytes = userBackupUsage[0]['archiveBytes'] 107 | logging.info(str(userBackupArchiveBytes)) 108 | 109 | printRow.extend([str(userBackupArchiveBytes)]) 110 | # else: 111 | # printUserEmptyBlock() 112 | 113 | else: 114 | printUserEmptyBlock() 115 | 116 | pUserQuotaInBytes = user['quotaInBytes'] 117 | printRow.extend([str(pUserQuotaInBytes)]) 118 | 119 | if int(userBackupArchiveBytes) > int(pUserQuotaInBytes) and int(pUserQuotaInBytes) != -1: 120 | overQuota = True 121 | printRow.extend([str(overQuota)]) 122 | 123 | logging.info(str(printRow)) 124 | csvUserFile.writerow(printRow) 125 | # print printRow 126 | 127 | 128 | def printUserEmptyBlock(): 129 | printRow.extend([emptyBlock]) 130 | printRow.extend([emptyBlock]) 131 | printRow.extend([emptyBlock]) 132 | printRow.extend([emptyBlock]) 133 | # printRow.extend([emptyBlock]) 134 | # printRow.extend([emptyBlock]) 135 | 136 | 137 | 138 | getUserReport() 139 | # print str(c42Lib.getDevicesPageCount()) 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated 2 | [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) 3 | 4 | Code42 recommends that customers visit [developer.code42.com](https://developer.code42.com) for current documentation and reference to current supported toolkits. 5 | 6 | --- 7 | # CrashPlan API Examples 8 | 9 | This project is a showcase area for various scripts that either Code42 or its customers have provided that run against the Code42 REST API and extend the functionality or reporting beyond what is possible in the default console. 10 | 11 | --- 12 | ## Folder Descriptions 13 | ### AccessLockUtility 14 | This Windows utility (.exe and powershell script provided) uses the Access Lock feature present in Code42 versions 6.0+ to remotely lock or unlock a device running the Code42 client application. For full readme information, see the `access_lock.pdf` file inside the directory. 15 | 16 | ### AuthorityMigrationHelpers 17 | If you're planning on migrating your Code42 authority to the cloud, these scripts will help you with the most common tasks needed to get your authority migrated. See `README.md` in this directory for more detailed info on the below scripts: 18 | 1. `roleChanger.py` - batch add or remove roles for users in your environment 19 | 2. `usernameToEmails.py` - change usernames to e-mail addresses using a few different methods 20 | 21 | ### CompiledScripts 22 | This folder contains compiled versions of scripts that appear elsewhere in this repository. No dependencies needed, just download and run the binary. 23 | 24 | ### FFS 25 | This Windows utility (.exe and powershell script provided) uses the Code42 API in order to return Forensic File Search events matching one or more search terms. 26 | 27 | ### c42SharedLibScripts 28 | This folder contains a number of all-purpose scripts built off of Code42's shared python library. See `README.md` in this folder for more information. 29 | 30 | --- 31 | ## Standalone Scripts 32 | 33 | #### NoBackupsInXDays.ps1 34 | This script will look for devices that have not completed a backup in the last X (Specified while running) days, but do NOT have a backup alert (Meaning they have some backup activity recently). It will output a CSV file with the username, device name, destination name, last activity date and the last completed backup date. 35 | 36 | #### addLDAPUsers.py 37 | Takes a comma-delimited CSV file of user names and adds those users to a LDAP Org. 38 | 39 | PRE-REQS: 40 | * Python 2.7 41 | * Requests Module 42 | * http://www.python-requests.org/ 43 | * PROe Server Host and Port 44 | * PROe Server Admin and Password 45 | * CSV file name 46 | * LDAP ORG ID. You can get this by navigating to the org and getting the org id value from the url. For example, `http://localhost:4280/console/app.html#v=orgs:overview&t=0mbunsjn5sbz91tlzg5clpd7qg&s=orgDetail&so[orgId]=3` would be an Org ID of 3. 47 | 48 | STEPS: 49 | 1. Create CSV file with comma-delmited list of LDAP users to add to the PROe Server. Save it in the same location as the addLDAPUsers.py script. 50 | 2. Update addLDAPUsers.py and add your environment values for cp_host, cp_port, etc. 51 | 3. Execute the script and check the addLDAPUsers.log file for your results. 52 | 53 | #### deactivateDevices.py 54 | Deactivates users devices based on the number of months since they have last connected to the authority server 55 | 56 | Params: 57 | * 1st arg - number of months (i.e 3) 58 | * 2nd arg - type of logging 59 | * values: `verbose`, `nonverbose` 60 | * 3rd arg - set to deactivate devices or only print the devices that will be deactivated, but not deactivate them. 61 | * values: `deactivate`, `print` 62 | 63 | Example usages: 64 | * `python deactivateDevices.py 3 verbose print` 65 | * `python deactivateDevices.py 3 noverbose deactivate` 66 | 67 | #### pushRestore.sh 68 | pushRestore.sh [sourceComputer] [destComputer] [restorePath] [getDirs] [getFiles] 69 | 70 | Example: 71 | 72 | `./pushRestore.sh 'COMP1' 'COMP2' '/tmp/restore' '/tmp/dir1,/tmp/dir 2' '/tmp/file1,/tmp/file 2'` 73 | 74 | This example script performs an automated Push Restore (via REST): 75 | * One or more source files/directories. 76 | * To local or remote destination computer. To remote directory of choice. 77 | * Using MPC or Cloud storage. 78 | * Recursive restore (includes subdirectories). 79 | * Between computers of different users (if Admin). 80 | * ...and more. 81 | 82 | Edit the variables near the top of this script, or pass in optional args. 83 | 84 | Notes: 85 | * Push destination should be running authenticated Code42 client. 86 | * Archive adoption (or original owner) not required. 87 | 88 | #### restoreWatch.py 89 | To prevent data leaks, detect restore activity of concern, and take 90 | action when such activity is detected. Actions include warning the 91 | admin, blocking admin, and blocking the user who has initiated the 92 | restores. 93 | 94 | The script produces the following output: 95 | 1. Email to administrator or designated recipient of warning messages. 96 | 2. CSV file with warnings, affected users, actions taken, and other 97 | information. This file is only created or appended to when a trigger 98 | event is detected! 99 | 3. restoreWatchData: a binary file that stores data needed by the script. 100 | This file is not user-viewable. 101 | 102 | See full description and instructions in the header of this script. 103 | -------------------------------------------------------------------------------- /c42SharedLibScripts/legacy_pre_4.2/getInfoByDeviceGUID.py: -------------------------------------------------------------------------------- 1 | # By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no cost separately from Code42's commercial offerings. 2 | # This software is not provided under Code42's master services agreement. 3 | # It is provided AS-IS, without support, and subject to the license below. 4 | # Any support and documentation for this software are available at the Code42 community site. 5 | 6 | # The MIT License (MIT) 7 | # Copyright (c) 2019 Code42 8 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 10 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 11 | # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 12 | # is furnished to do so, subject to the following conditions: 13 | 14 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 17 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | # File: getInfoByDeviceGUID.py 22 | # Author: AJ LaVenture, Code 42 Software 23 | # Last Modified: 08-23-2013 24 | # 25 | # From a list of Device GUIDS, pull device and user information into CSV Format 26 | # 27 | 28 | from c42SharedLibrary import c42Lib 29 | import math 30 | import sys 31 | import json 32 | import csv 33 | import base64 34 | import logging 35 | import requests 36 | import math 37 | from dateutil.relativedelta import * 38 | import datetime 39 | import calendar 40 | import getpass 41 | 42 | 43 | #Test values 44 | c42Lib.cp_host = "http://aj-proappliance" 45 | c42Lib.cp_port = "4280" 46 | c42Lib.cp_username = "admin" 47 | c42Lib.cp_password = getpass.getpass('Enter your CrashPlan console password: ') # You will be prompted for your password 48 | 49 | 50 | c42Lib.cp_logLevel = "INFO" 51 | c42Lib.cp_logFileName = "getInfoByDeviceGUID.log" 52 | c42Lib.setLoggingLevel() 53 | 54 | 55 | AJ_TEST_DEVICE_GUID_LIST = [597897713966645505,597724554922036354,597893957280654977,597153934331668098] 56 | # AJ_TEST_DEVICE_GUID_LIST = [597897713966645505] 57 | # AJ_TEST_DEVICE_GUID_LIST = [597724554922036354] 58 | # AJ_TEST_DEVICE_GUID_LIST = [597893957280654977] 59 | # AJ_TEST_DEVICE_GUID_LIST = [597153934331668098] 60 | 61 | 62 | GUID_LIST = AJ_TEST_DEVICE_GUID_LIST 63 | 64 | 65 | csvFile = csv.writer(open("deviceReport.csv", "wb+")) 66 | csvHeaders = ['guid','deviceName','osName','lastCompletedBackup','percentComplete','archiveBytes','selectedBytes','userId','userName','email', 67 | 'firstName','lastName'] 68 | 69 | csvFile.writerow(csvHeaders) 70 | print csvHeaders 71 | 72 | for index, guid in enumerate(GUID_LIST): 73 | printValues = [] 74 | printValues.extend([str(guid)]) 75 | 76 | deviceList = c42Lib.getDeviceByGuid(guid) 77 | deviceObject = deviceList['computers'] 78 | # print deviceObject 79 | 80 | deviceName = deviceObject[0]['name'] 81 | printValues.extend([deviceName.encode('utf-8')]) 82 | 83 | deviceOsName = deviceObject[0]['osName'] 84 | printValues.extend([deviceOsName.encode('utf-8')]) 85 | 86 | if deviceObject[0]['backupUsage']: 87 | 88 | deviceObjectBackupUsage = deviceObject[0]['backupUsage'] 89 | 90 | lastCompletedBackup = deviceObjectBackupUsage[0]['lastCompletedBackup'] 91 | # print str(lastCompletedBackup) 92 | printValues.extend([str(lastCompletedBackup)]) 93 | 94 | percentComplete = deviceObjectBackupUsage[0]['percentComplete'] 95 | printValues.extend([str(percentComplete)]) 96 | 97 | archiveBytes = deviceObjectBackupUsage[0]['archiveBytes'] 98 | printValues.extend([str(archiveBytes)]) 99 | 100 | selectedBytes = deviceObjectBackupUsage[0]['selectedBytes'] 101 | printValues.extend([str(selectedBytes)]) 102 | 103 | else: 104 | emptyBlock = "" 105 | printValues.extend([emptyBlock]) 106 | printValues.extend([emptyBlock]) 107 | printValues.extend([emptyBlock]) 108 | printValues.extend([emptyBlock]) 109 | 110 | userId = deviceObject[0]['userId'] 111 | printValues.extend([str(userId)]) 112 | 113 | userList = c42Lib.getUserById(userId) 114 | # userObject = userList['users'] 115 | 116 | username = userList['username'] 117 | printValues.extend([username.encode('utf-8')]) 118 | 119 | email = userList['email'] 120 | printValues.extend([email.encode('utf-8')]) 121 | 122 | firstName = userList['firstName'] 123 | printValues.extend([firstName.encode('utf-8')]) 124 | 125 | lastName = userList['lastName'] 126 | printValues.extend([lastName.encode('utf-8')]) 127 | 128 | logging.info(printValues) 129 | csvFile.writerow(printValues) 130 | print printValues 131 | 132 | 133 | 134 | 135 | # script request for fahad: 136 | 137 | # list of device guids 138 | 139 | 140 | # lastCompletedBackup 141 | # percentComplete 142 | # archiveBytes (bytes already backed up) 143 | # selectedBytes 144 | 145 | # userId 146 | 147 | 148 | # get user id and this info 149 | # username 150 | # email 151 | # firstName 152 | # lastName 153 | 154 | 155 | 156 | # list object = 157 | 158 | # add csv 159 | -------------------------------------------------------------------------------- /c42SharedLibScripts/legacy_pre_4.2/pushRestore.py: -------------------------------------------------------------------------------- 1 | # By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no cost separately from Code42's commercial offerings. 2 | # This software is not provided under Code42's master services agreement. 3 | # It is provided AS-IS, without support, and subject to the license below. 4 | # Any support and documentation for this software are available at the Code42 community site. 5 | 6 | # The MIT License (MIT) 7 | # Copyright (c) 2019 Code42 8 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 10 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 11 | # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 12 | # is furnished to do so, subject to the following conditions: 13 | 14 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 17 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | # File: pushRestore.py 22 | # Author: Amir Kader 23 | # Last Modified: 01/31/2015 24 | # 25 | # This sample uses the WebRestoreSession api to do a push restore 26 | # of a file from a device archive of one computer, sourceComputer, 27 | # to loacation on another computer, targetComputer. 28 | # Make sure to set the values of the following variable in the script (current script doesn't take input params): 29 | # - sourceComputer : device name of the source computer you want restore the file from 30 | # - targetComputer : device name of the target computer you want to push restore to 31 | # - restoreFile : the path and file name of the file you want to restore 32 | # - restoreFilePath : the directory location on the target device to push the restore to 33 | # 34 | # This sample is dependent on the c42SharedLibrary.py 35 | # The c4ShardLibary.py needs to include the variable settings for: 36 | # cp_api_webRestoreSession = "/api/WebRestoreSession" 37 | # cp_api_dataKeyToken = "/api/DataKeyToken" 38 | # cp_api_pushRestoreJob = "/api/pushRestoreJob" 39 | # 40 | # This script has been tested with 4.x releases of the CODE42 Edge Platform 41 | # 42 | 43 | from c42SharedLibrary import c42Lib 44 | import sys 45 | import csv 46 | import getpass 47 | import os 48 | import ntpath 49 | import json 50 | 51 | from collections import defaultdict 52 | 53 | # Test values 54 | c42Lib.cp_host = "http://demo.code42.com" 55 | c42Lib.cp_port = "4280" 56 | 57 | master_cp_host = c42Lib.cp_host 58 | master_cp_port = c42Lib.cp_port 59 | 60 | c42Lib.cp_username = "admin" 61 | c42Lib.cp_password = getpass.getpass() 62 | c42Lib.cp_logLevel = "INFO" 63 | c42Lib.cp_logFileName = "pushRestore.log" 64 | c42Lib.setLoggingLevel() 65 | 66 | sourceComputer="C02M50TZFH04" 67 | targetComputer="ROBERTS-WIN7" 68 | restoreFile="/Users/amir.kader/Documents/test/amir.png" 69 | restorePath="C:/Users/Robert/Desktop/restore" 70 | 71 | print 'calling api/Computer to get guid of target computer: ' + targetComputer 72 | params = {} 73 | params['q'] = targetComputer 74 | params['active'] = 'true' 75 | payload = {} 76 | r = c42Lib.executeRequest("get", c42Lib.cp_api_computer, params, payload) 77 | content = r.content 78 | binary = json.loads(content) 79 | targetGUID = binary['data']['computers'][0]['guid'] 80 | print 'target computers guid: ' + targetGUID 81 | 82 | print 'calling api/Computer to get guid of source computer: ' + sourceComputer + ' and serverName...' 83 | params = {} 84 | params['q'] = sourceComputer 85 | params['incBackupUsage'] = 'true' 86 | params['active'] = 'true' 87 | 88 | payload = {} 89 | r = c42Lib.executeRequest("get", c42Lib.cp_api_computer, params, payload) 90 | content = r.content 91 | binary = json.loads(content) 92 | 93 | computers = binary['data']['computers'] 94 | srcGUID = computers[0]['guid'] 95 | backupUsage = computers[0]['backupUsage'] 96 | serverName = backupUsage[0]['serverName'] 97 | #print computers 98 | print 'source guid: ' + srcGUID 99 | print 'serverName: ' + serverName 100 | 101 | print 'calling api/Server?q=' + serverName + ' to get destinationServerGuid...' 102 | params = {} 103 | params['q'] = serverName 104 | 105 | r = c42Lib.executeRequest("get", c42Lib.cp_api_server, params, payload) 106 | content = r.content 107 | binary = json.loads(content) 108 | #print binary['data'] 109 | serverGUID = binary['data']['servers'][0]['guid'] 110 | print 'server guid: ' + serverGUID 111 | 112 | 113 | print 'calling api/DataKeyToken with payload of: ' 114 | params = {} 115 | payload = {'computerGuid': srcGUID} 116 | print payload 117 | print ' to get value of dataKeyToken' 118 | r = c42Lib.executeRequest("post", c42Lib.cp_api_dataKeyToken, params, payload) 119 | content = r.content 120 | binary = json.loads(content) 121 | dataKeyToken = binary['data']['dataKeyToken'] 122 | print 'dataKeyToken: ' + dataKeyToken 123 | 124 | print 'calling api/WebRestoreSession with a payload of : ' 125 | params = {} 126 | payload = {'computerGuid': srcGUID, 'dataKeyToken' : dataKeyToken} 127 | print payload 128 | print 'to get value of webRestoreSessionId' 129 | print c42Lib.cp_api_webRestoreSession 130 | r = c42Lib.executeRequest("post", c42Lib.cp_api_webRestoreSession, params, payload) 131 | 132 | content = r.content 133 | binary = json.loads(content) 134 | print binary 135 | webRestoreSessionId = binary['data']['webRestoreSessionId'] 136 | print 'webRestoreSessionId: ' + webRestoreSessionId 137 | print 'building payload for push restore..' 138 | file1 = [{'type' : "file", 'path' : restoreFile, 'selected': True}] 139 | #print(file1) 140 | payload = { 141 | 'webRestoreSessionId' : webRestoreSessionId, 142 | 'sourceGuid' : srcGUID, 143 | 'targetNodeGuid' : serverGUID, 144 | 'acceptingGuid' : targetGUID, 145 | 'restorePath' : restorePath, 146 | 'pathSet' : file1, 147 | 'numBytes' : 1, 148 | 'numFiles' : 1, 149 | 'showDeleted' : True, 150 | 'restoreFullPath': True 151 | } 152 | #print payload 153 | print json.dumps(payload, indent=4) 154 | r = c42Lib.executeRequest("post", c42Lib.cp_api_pushRestoreJob, params, payload) 155 | content = r.content 156 | binary = json.loads(content) 157 | #print json.dumps(binary, indent=4) 158 | restoreID = binary['data']['restoreId'] 159 | print "restoreID is: " + restoreID 160 | 161 | 162 | -------------------------------------------------------------------------------- /c42SharedLibScripts/legacy_pre_4.2/allDeviceReport.py: -------------------------------------------------------------------------------- 1 | # By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no cost separately from Code42's commercial offerings. 2 | # This software is not provided under Code42's master services agreement. 3 | # It is provided AS-IS, without support, and subject to the license below. 4 | # Any support and documentation for this software are available at the Code42 community site. 5 | 6 | # The MIT License (MIT) 7 | # Copyright (c) 2019 Code42 8 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 10 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 11 | # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 12 | # is furnished to do so, subject to the following conditions: 13 | 14 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 17 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | # 22 | # File: allDeviceReport.py 23 | # Author: P. Hirst & AJ LaVenture, Code 42 Software 24 | # Last Modified: 01-24-2014 25 | # 26 | # get report data on all users and devices 27 | # need to set the "destId" to ensure 28 | # 29 | # 30 | 31 | from c42SharedLibrary import c42Lib 32 | import math 33 | import sys 34 | import json 35 | import csv 36 | import base64 37 | import logging 38 | import requests 39 | import math 40 | from dateutil.relativedelta import * 41 | import datetime 42 | import calendar 43 | import getpass 44 | 45 | c42Lib.cp_host = "https://aj-ubuntu" 46 | c42Lib.cp_port = "4285" 47 | c42Lib.cp_username = "admin" 48 | c42Lib.cp_password = getpass.getpass('Enter your CrashPlan console password: ') 49 | 50 | # Set id of primary destination to get information and stats about 51 | destId = 15 52 | 53 | c42Lib.cp_logLevel = "DEGUG" 54 | c42Lib.cp_logFileName = "allDeviceReport.log" 55 | c42Lib.setLoggingLevel() 56 | 57 | 58 | # c42Lib.MAX_PAGE_NUM = 5 59 | 60 | # cp_api_storedBytesHistory = "/api/storedBytesHistory" 61 | 62 | 63 | emptyBlock = "" 64 | 65 | csvDeviceHeader = ['userId', 'username', 'computerId','computerName','computerType','osName','computerStatus','storePointId','storePointName','serverId','serverName', 66 | 'selectedFiles','selectedBytes','selectedBytesHR','archiveBytes','archiveBytesHR','percentComplete','lastCompletedBackup', 67 | 'CompArchive-oldestDate','CompArchive-oldestDateBytes','oldestHR','CompArchive-newestDate','CompArchive-newestDateBytes','newsetHR','CompArchive-Delta','deltaHR'] 68 | printValues = [] 69 | printRow = [] 70 | 71 | def findRecord(key,records): 72 | for r in records: 73 | if r['userId'] == key: 74 | return r 75 | 76 | 77 | def getDeviceReport(): 78 | csvDeviceFile = csv.writer(open("allDeviceReport.csv", "wb+")) 79 | csvDeviceFile.writerow(csvDeviceHeader) 80 | 81 | users = c42Lib.getAllUsers() 82 | # get all the users to have in memory 83 | 84 | devices = c42Lib.getAllDevices() 85 | # devices = c42Lib.getDevices(1) 86 | 87 | for index, device in enumerate(devices): 88 | # device = c42Lib.getDeviceById(8637) 89 | printValues = [] 90 | 91 | # computerId = loopDevice['computerId'] 92 | 93 | # device = c42Lib.getDeviceById(computerId) 94 | 95 | userId = device['userId'] 96 | printValues.extend([str(userId)]) 97 | 98 | # Lookup User Info 99 | userInfo = findRecord(userId,users) 100 | 101 | logging.debug(userInfo) 102 | 103 | #Get User Name 104 | if userInfo: 105 | userName = userInfo['username'] 106 | else: 107 | username = 'null' 108 | printValues.extend([userName.encode('utf-8')]) 109 | 110 | # print deviceObject 111 | computerId = device['computerId'] 112 | printValues.extend([str(computerId)]) 113 | 114 | computerName = device['name'] 115 | printValues.extend([computerName.encode('utf-8')]) 116 | 117 | computerType = device['type'] 118 | printValues.extend([computerType.encode('utf-8')]) 119 | 120 | osName = device['osName'] 121 | printValues.extend([osName.encode('utf-8')]) 122 | 123 | computerStatus = device['status'] 124 | printValues.extend([computerStatus.encode('utf-8')]) 125 | 126 | 127 | if 'backupUsage' in device: 128 | 129 | deviceObjectBackupUsage = device['backupUsage'] 130 | 131 | for backupUsage in deviceObjectBackupUsage: 132 | 133 | if backupUsage['targetComputerId'] == destId: 134 | 135 | storePointId = backupUsage['storePointId'] 136 | printValues.extend([str(storePointId)]) 137 | 138 | storePointName = backupUsage['storePointName'] 139 | printValues.extend([storePointName.encode('utf-8')]) 140 | 141 | serverId = backupUsage['serverId'] 142 | printValues.extend([str(serverId)]) 143 | 144 | serverName = backupUsage['serverName'] 145 | printValues.extend([serverName.encode('utf-8')]) 146 | 147 | selectedFiles = backupUsage['selectedFiles'] 148 | printValues.extend([str(selectedFiles)]) 149 | 150 | selectedBytes = backupUsage['selectedBytes'] 151 | printValues.extend([str(selectedBytes)]) 152 | 153 | printValues.extend([c42Lib.sizeof_fmt(selectedBytes)]) 154 | 155 | archiveBytes = backupUsage['archiveBytes'] 156 | printValues.extend([str(archiveBytes)]) 157 | 158 | printValues.extend([c42Lib.sizeof_fmt(archiveBytes)]) 159 | 160 | percentComplete = backupUsage['percentComplete'] 161 | printValues.extend([str(percentComplete)]) 162 | 163 | lastCompletedBackup = backupUsage['lastCompletedBackup'] 164 | printValues.extend([str(lastCompletedBackup)]) 165 | 166 | 167 | if 'history' in backupUsage: 168 | 169 | backupHistory = backupUsage['history'] 170 | 171 | if backupHistory: 172 | 173 | oldestDate = 9999999999999999999 174 | # "20130801": 1909986568291, 175 | oldestDateBytes = 0 176 | newestDate = 0 177 | # "20130830": 1932985363857 178 | newestDateBytes = 0 179 | 180 | for singleHistory in backupHistory: 181 | # print key, value 182 | # print oldestDate, int(key) 183 | currDate = singleHistory['date'].replace('-','') 184 | 185 | currarchiveBytes = singleHistory['archiveBytes'] 186 | 187 | if (oldestDate > int(currDate)): 188 | oldestDate = int(currDate) 189 | oldestDateBytes = currarchiveBytes 190 | 191 | # print newestDate, int(key) 192 | if (newestDate <= int(currDate)): 193 | newestDate = int(currDate) 194 | newestDateBytes = currarchiveBytes 195 | 196 | printValues.extend([str(oldestDate)]) 197 | printValues.extend([str(oldestDateBytes)]) 198 | printValues.extend([c42Lib.sizeof_fmt(oldestDateBytes)]) 199 | 200 | printValues.extend([str(newestDate)]) 201 | printValues.extend([str(newestDateBytes)]) 202 | printValues.extend([c42Lib.sizeof_fmt(newestDateBytes)]) 203 | 204 | pArchiveBytesDeltaMonth = newestDateBytes - oldestDateBytes 205 | printValues.extend([str(pArchiveBytesDeltaMonth)]) 206 | printValues.extend([c42Lib.sizeof_fmt(pArchiveBytesDeltaMonth)]) 207 | # else: 208 | # printDeviceEmptyBlock(8) 209 | # else: 210 | # printDeviceEmptyBlock(8) 211 | # else: 212 | # print("hi") 213 | # printDeviceEmptyBlock(19) 214 | 215 | # else: 216 | # print("bye") 217 | # printDeviceEmptyBlock(19) 218 | 219 | # print ', '.join(printValues) 220 | logging.info(str(printValues)) 221 | csvDeviceFile.writerow(printValues) 222 | 223 | 224 | 225 | def printDeviceEmptyBlock(max): 226 | for x in range(int(max)): 227 | printValues.extend([emptyBlock]) 228 | 229 | 230 | 231 | getDeviceReport() 232 | # print str(c42Lib.getDevicesPageCount()) 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | -------------------------------------------------------------------------------- /SearchChecksum.sh: -------------------------------------------------------------------------------- 1 | # By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no cost separately from Code42's commercial offerings. 2 | # This software is not provided under Code42's master services agreement. 3 | # It is provided AS-IS, without support, and subject to the license below. 4 | # Any support and documentation for this software are available at the Code42 community site. 5 | 6 | # The MIT License (MIT) 7 | # Copyright (c) 2019 Code42 8 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 10 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 11 | # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 12 | # is furnished to do so, subject to the following conditions: 13 | 14 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 17 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | #!/bin/bash 22 | # 23 | # usage: SearchCheckum.sh 24 | # 25 | # This program searches CrashPlan Archives for directories and files with a specific MD5 checksum. It was adapted from SearchPath.sh 26 | # 27 | # 28 | # Created: by Jeff Dugan 12/9/14 (original version is named SearchPath.sh ) 29 | # Modified and adapted by Todd Ojala 2/5/2015 30 | # Version Info 31 | # V1.0 - Base Script - Jeff Dugan - 12/9/14 32 | # V1.1 - Added case sensitivity logic and accounted for spaces in string - Kyle Hatelstad - 12/22/14 33 | # V2.0 - Adapted to use the storePoint and server resources, to reflect our changed data model. This version also searches for MD5 checksums rather than an arbitrary 34 | # string in the file path. See the updated SearchPath.sh for the ability to search for an arbitrary string in the pathname. - Todd Ojala - 2/5/2015 35 | 36 | 37 | echo "*************************************************************" 38 | echo "——————————————————————————Code42—————————————————————————————" 39 | echo "This is an unofficial script that is not supported." 40 | echo "Feel free to modify and use as you see fit." 41 | echo "*************************************************************" 42 | echo "" 43 | echo "*************************************************************" 44 | echo "———————————————————————SearchChecksum.sh—————————————————————————" 45 | echo "This script searches CrashPlan Archives for directories and" 46 | echo "files with a specific MD5 checksum." 47 | echo "Output is directed to the folder the script is run in." 48 | echo "It is designed for Private Deployments only." 49 | echo "*************************************************************" 50 | echo "" 51 | echo "" 52 | 53 | 54 | # Collect Master Appliance info 55 | 56 | echo "Enter Your Master Server IP" 57 | read MASTER 58 | 59 | echo "Enter Your Master Server Management Port" 60 | read PORT 61 | 62 | 63 | #Set HTTP: or HTTPS: 64 | if [ "$PORT" == "4280" ] 65 | then 66 | PROTOCOL="http" 67 | else 68 | PROTOCOL="https" 69 | fi 70 | 71 | # Collect User info to connect to perform search 72 | echo "Enter Your UserID" 73 | read C42UID 74 | 75 | echo "Enter your Password" 76 | read -s C42PASS 77 | 78 | echo "Enter the MD5 checksum to search for:" 79 | read CHECKSUM 80 | 81 | 82 | echo "At which level would you like to search. all, org, device?" 83 | read LEVEL 84 | 85 | #Set timestamp for Filename 86 | TIMESTAMP=$(date +"%Y%m%d%H%M%S") 87 | 88 | 89 | 90 | #Gather computer guids at the appropriate level. 91 | case $LEVEL in 92 | org) 93 | #Gather ORG Registration to determine Organization ID 94 | echo "Enter Organization Registration Key XXXX-XXXX-XXXX-XXXX" 95 | read ORGREG 96 | ORGID=$(curl -sku $C42UID:$C42PASS "$PROTOCOL://$MASTER:$PORT/api/Org?q=$ORGREG" | python -mjson.tool | grep orgId | tr -cd '[:digit:]') 97 | 98 | #Use Organization ID to get a list of all guids of service type CrashPlan in the given Org. 99 | TEMP=$(curl -sku $C42UID:$C42PASS "$PROTOCOL://$MASTER:$PORT/api/computer?orgId=$ORGID" | python -mjson.tool | grep 'guid\|service' | awk -F "\"" '{print $4}') 100 | TEMPARRAY=($TEMP) 101 | COUNT=0 102 | INDEX=0 103 | while [ $COUNT -lt ${#TEMPARRAY[@]} ] 104 | do 105 | if [ ${TEMPARRAY[COUNT+1]} == "CrashPlan" ] 106 | 107 | then 108 | ARRAY[$INDEX]=${TEMPARRAY[COUNT]} 109 | INDEX=$(($INDEX + 1)) 110 | fi 111 | COUNT=$(($COUNT + 2)) 112 | done 113 | 114 | #Set Filename 115 | FILENAME=("SearchPath_Org_""$ORGID""_""$TIMESTAMP"".csv") 116 | ;; 117 | 118 | all) 119 | #Get a list of all guids of service type CrashPlan. 120 | TEMP=$(curl -sku $C42UID:$C42PASS "$PROTOCOL://$MASTER:$PORT/api/computer" | python -mjson.tool | grep 'guid\|service' | awk -F "\"" '{print $4}') 121 | TEMPARRAY=($TEMP) 122 | 123 | COUNT=0 124 | INDEX=0 125 | while [ $COUNT -lt ${#TEMPARRAY[@]} ] 126 | do 127 | if [ ${TEMPARRAY[COUNT+1]} == "CrashPlan" ] 128 | 129 | then 130 | ARRAY[$INDEX]=${TEMPARRAY[COUNT]} 131 | INDEX=$(($INDEX + 1)) 132 | fi 133 | COUNT=$(($COUNT + 2)) 134 | done 135 | 136 | #Set Filename 137 | FILENAME=("SearchPath_Org_all_""$TIMESTAMP"".csv") 138 | ;; 139 | 140 | device) 141 | echo "Enter Device GUID" 142 | read DEVICE 143 | ARRAY=$DEVICE 144 | #ARRAY=662507403751203534 145 | 146 | #Set Filename 147 | FILENAME=("SearchPath_GUID_""$DEVICE""_""$TIMESTAMP"".csv") 148 | ;; 149 | 150 | *) 151 | echo "INVALID OPTION. Only all, org and device accepted." 152 | exit 1 153 | ;; 154 | esac 155 | 156 | #List Header 157 | echo "USERNAME,USERID,COMPUTERNAME,GUID,PATH" >> $FILENAME 158 | 159 | COUNT=0 160 | HITS=0 161 | while [ $COUNT -lt ${#ARRAY[@]} ] 162 | do 163 | 164 | #get Username + UserID from computer guid 165 | DEVICE=${ARRAY[COUNT]} 166 | USERINFO=$(curl -sku $C42UID:$C42PASS "$PROTOCOL://$MASTER:$PORT/api/computer/$DEVICE?idType=guid" | python -mjson.tool) 167 | USERID=$(echo "$USERINFO" | grep '"userId":' | tr -cd '[:digit:]') 168 | USERNAME=$(curl -sku $C42UID:$C42PASS "$PROTOCOL://$MASTER:$PORT/api/user/$USERID" | python -mjson.tool | grep '"username":') 169 | USERNAME=$(echo ${USERNAME##*:}) 170 | USERNAME=$(echo ${USERNAME#\"}) 171 | USERNAME=$(echo ${USERNAME%\"\,}) 172 | 173 | #get Computername from computer guid 174 | COMPNAME=$(echo "$USERINFO" | grep '"name":') 175 | COMPNAME=$(echo ${COMPNAME##*:} | tr -cd '[:alnum:]') 176 | 177 | #get archiveGuid from computer guid using Archive resource, as you can no longer go directly to ArchiveMetadata with a computer guid (Todd 1/27/2015) 178 | ARCHIVE_CALL=$(curl -sku $C42UID:$C42PASS "$PROTOCOL://$MASTER:$PORT/api/archive?backupSourceGuid=$DEVICE") 179 | ARCHIVE=$(echo $ARCHIVE_CALL | python -mjson.tool | grep '"archiveGuid":') 180 | ARCHIVE=$(echo ${ARCHIVE##*:}) 181 | ARCHIVE=$(echo ${ARCHIVE#\"}) 182 | ARCHIVE=$(echo ${ARCHIVE%\"\,}) 183 | 184 | #Get the storePointId from the Archive resource. It does not make another call to the Archive resource, but extracts from the variable ARCHIVE_CALL above 185 | STOREPOINT=$(echo $ARCHIVE_CALL | python -mjson.tool | grep '"storePointId":') 186 | STOREPOINT=$(echo ${STOREPOINT##*:}) 187 | STOREPOINT=$(echo ${STOREPOINT%\,}) 188 | 189 | #Get the ServerId using the storePoint resource (using the storePointId acquired in the previous section) 190 | SERVER=$(curl -sku $C42UID:$C42PASS "$PROTOCOL://$MASTER:$PORT/api/storePoint/$STOREPOINT" | python -mjson.tool | grep '"serverId":') 191 | SERVER=$(echo ${SERVER##*:}) 192 | SERVER=$(echo ${SERVER%\,}) 193 | 194 | #Get the server address or IP from the Server resource (using the serverId from previous section) 195 | STORAGE_SERVER=$(curl -sku $C42UID:$C42PASS "$PROTOCOL://$MASTER:$PORT/api/server/$SERVER" | python -mjson.tool | grep '"primaryAddress":') 196 | STORAGE_SERVER=$(echo ${STORAGE_SERVER##*ss\":}) 197 | STORAGE_SERVER=$(echo ${STORAGE_SERVER#\"}) 198 | STORAGE_SERVER=$(echo ${STORAGE_SERVER%\"\,}) 199 | STORAGE_SERVER=$(echo ${STORAGE_SERVER%:4282}) 200 | 201 | #Test for Archive Metadata output to avoid JSON decoding errors 202 | TESTNULL=$(curl -sku $C42UID:$C42PASS "https://$STORAGE_SERVER:4285/api/ArchiveMetadata/$ARCHIVE?decryptPaths=true" | grep "$CHECKSUM") 203 | 204 | if [ -n "$TESTNULL" ] 205 | then 206 | #Get data, extract paths, and print 207 | curl -sku $C42UID:$C42PASS "https://$STORAGE_SERVER:4285/api/ArchiveMetadata/$ARCHIVE?decryptPaths=true" | python -mjson.tool| grep -B3 "$CHECKSUM" | grep "path\"" | awk -F "\"" '{print "'$USERNAME'," "'$USERID'," "'$COMPNAME'," "'$DEVICE'," $4}' >> $FILENAME 208 | 209 | #Increment counter of number of Guids with positive match 210 | HITS=$(($HITS +1)) 211 | fi 212 | 213 | COUNT=$(($COUNT + 1)) 214 | 215 | done 216 | 217 | #Print results summary to terminal 218 | echo "Number of Devices scanned" 219 | echo $COUNT 220 | echo "Number of Devices matching search" 221 | echo $HITS 222 | echo "Percentage positive search" 223 | if [ $COUNT != 0 ] 224 | then 225 | echo "scale=1;$HITS/$COUNT*100"| bc 226 | else 227 | echo "N/A - No Devices Found" 228 | fi 229 | echo "Results can be found in the following file where the script is located." 230 | echo $FILENAME 231 | 232 | exit 0 233 | -------------------------------------------------------------------------------- /FFS/ffs_example.ps1: -------------------------------------------------------------------------------- 1 | # By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no cost separately from Code42's commercial offerings. 2 | # This software is not provided under Code42's master services agreement. 3 | # It is provided AS-IS, without support, and subject to the license below. 4 | # Any support and documentation for this software are available at the Code42 community site. 5 | 6 | # The MIT License (MIT) 7 | # Copyright (c) 2019 Code42 8 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 10 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 11 | # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 12 | # is furnished to do so, subject to the following conditions: 13 | 14 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 17 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | # 22 | # Code42 Forensic File Search Example Script - v1.1 23 | # 24 | # Description: This script will perform a Forensic File Search via the API. It 25 | # can perform a single search or be fed a list of search terms. 26 | # 27 | # https://support.code42.com/Administrator/Cloud/Administration_console_reference/Forensic_File_Search_reference_guide 28 | # 29 | # Author: Kyle Hatlestad 30 | # 31 | # 32 | 33 | ######################################################## 34 | # Catch parameters passed to script 35 | ######################################################## 36 | Param( 37 | [alias("u")] 38 | [string]$user, 39 | [alias("p")] 40 | [string]$password 41 | ) 42 | 43 | $scriptVersion = "1.1" 44 | ######################################################## 45 | # Functions 46 | ######################################################## 47 | #---------------------------------------------------------------------------------------# 48 | # Error handling function # 49 | #---------------------------------------------------------------------------------------# 50 | function errorHandler { 51 | param( [Parameter(Mandatory=$false)][string]$errorMessage) 52 | Write-Host "Error: " $_; Write-Host "`n"; Write-Host "Terminating script."; Write-Host "`n" 53 | & cmd /c pause 54 | Exit 55 | } 56 | 57 | ################################### 58 | # Variables (Customize) 59 | ################################### 60 | [string]$global:ContentType = "application/json" 61 | [string]$FullCPServerAddress = "https://console.us.code42.com" 62 | [string]$FullSTSServerAddress = "https://sts-east.us.code42.com" 63 | 64 | ################################### 65 | # Intro Screen 66 | ################################### 67 | 68 | Write-Host "####################################################################################" 69 | Write-Host "`n" 70 | Write-Host " Code42 Forensic File Search Example Script" 71 | Write-Host "`n" 72 | Write-Host " Script Version: " $scriptVersion 73 | Write-Host "`n" 74 | Write-Host " Description: This script will perform a Forensic File Search via the API. It " 75 | Write-Host " shows how a call is made and results taht come back." 76 | Write-Host "`n" 77 | Write-Host " Requirements:" 78 | Write-Host " * Code42 Cloud" 79 | Write-Host " * Code42 Security Center Product" 80 | Write-Host " * User with either Customer Cloud Admin or Security Center User role" 81 | Write-Host " * Windows 10 Client with Windows PowerShell" 82 | Write-Host " * You must run Internet Explorer (not Edge) at least once for the initial configuration" 83 | Write-Host "`n" 84 | Write-Host "Command-line Arguments:" 85 | Write-Host " -u" 86 | Write-Host " Username" 87 | Write-Host " -p" 88 | Write-Host " Password" 89 | Write-Host "`n" 90 | Write-Host "####################################################################################" 91 | 92 | ################################### 93 | # Inputs 94 | ################################### 95 | # Username and Password prompt if not passed in 96 | 97 | IF([string]::IsNullOrEmpty($user)) { 98 | $CPUserName = Read-Host -Prompt 'Input username' 99 | } else { 100 | $CPUserName = $user 101 | } 102 | 103 | IF([string]::IsNullOrEmpty($password)) { 104 | $CPSecureUserPassword = Read-Host 'Input password' -AsSecureString 105 | $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($CPSecureUserPassword) 106 | $CPUserPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) 107 | } else { 108 | $CPUserPassword = $password 109 | } 110 | 111 | $EncodedAuthorization = [System.Text.Encoding]::UTF8.GetBytes($CPUserName + ':' + $CPUserPassword) 112 | $EncodedPassword = [System.Convert]::ToBase64String($EncodedAuthorization) 113 | $headers = @{"Authorization" = "Basic $($EncodedPassword)"} 114 | 115 | # Prompt for search parameters 116 | 117 | #Search Term 118 | Write-Host "Select the Search Term" 119 | Write-Host "*1 - Filename" 120 | Write-Host "2 - File Path" 121 | Write-Host "3 - MD5 Hash" 122 | Write-Host "4 - Hostname" 123 | Write-Host "5 - Username" 124 | 125 | $getSearchTerm = Read-Host -Prompt "Enter choice (1-5)" 126 | $searchTerm = switch ($getSearchTerm) 127 | { 128 | 1 {'fileName'} 129 | 2 {'filePath'} 130 | 3 {'md5Checksum'} 131 | 4 {'osHostName'} 132 | 5 {'deviceUserName'} 133 | default {'fileName'} 134 | } 135 | 136 | Write-Host "Select the Operator" 137 | Write-Host "*1 - Is" 138 | Write-Host "2 - Is Not" 139 | 140 | $getSearchOperator = Read-Host -Prompt "Enter choice (1-2)" 141 | $searchOperator = switch ($getSearchOperator) 142 | { 143 | 1 {'IS'} 144 | 2 {'IS_NOT'} 145 | default {'IS'} 146 | } 147 | 148 | DO { 149 | $searchValue = Read-Host -Prompt 'Input your search value' 150 | } WHILE ([string]::IsNullOrEmpty($searchValue)) 151 | 152 | 153 | ################################### 154 | # Verify user has proper permissions 155 | ################################### 156 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 157 | $CPEncodedUserName = [uri]::EscapeDataString($CPUserName) 158 | $resourceurl = '/api/User?incAll=true&active=true&q=' + $CPEncodedUserName 159 | $uri = $FullCPServerAddress + $resourceurl 160 | 161 | try { 162 | 163 | $securityUser = invoke-RestMethod -Uri $uri -Method GET -Headers $headers 164 | } catch { 165 | errorHandler $_ 166 | } 167 | 168 | if ($securityUser.data.users.roles -contains "Security Center User" -Or $securityUser.data.users.roles -contains "Customer Cloud Admin") { 169 | #Permisions are good. They have one of the right roles 170 | } else { 171 | Write-Host "`n"; Write-Host "User " $CPUserName " does not have permissions to run Forensic File Search. The role 'Security Center User' or 'Customer Cloud Admin' is required." 172 | errorHandler $_ 173 | } 174 | 175 | ###################################################### 176 | # Authenticate and establish Authorization Token 177 | ###################################################### 178 | 179 | # Call the login-user method on the Secure Token Server to create the Code42 V3 User Token 180 | $resourceurl = '/api/v1/login-user' 181 | $uri = $FullSTSServerAddress + $resourceurl 182 | 183 | try { 184 | $requestResponse = invoke-WebRequest -Uri $uri -Method GET -Headers $headers -SessionVariable myWebSession 185 | } catch { 186 | errorHandler $_ 187 | } 188 | 189 | # From the resonse, pull out the v3_user_token value to pass into the header for the FFS calls 190 | $authBody = $requestResponse | ConvertFrom-Json 191 | $v3Token = $authBody.v3_user_token 192 | 193 | # Set the parameters based on the input. 194 | # Build FFS search terms into a JSON message 195 | $json = '{ 196 | "groups": [ 197 | { 198 | "filters": [ 199 | { 200 | "operator": "' + $searchOperator + '", 201 | "term": "' + $searchTerm + '", 202 | "value": "' + $searchValue + '" 203 | } 204 | ] 205 | } 206 | ], 207 | "pgNum": 1, 208 | "pgSize": 10000, 209 | "srtDir": "asc", 210 | "srtKey": "eventTimestamp" 211 | }' 212 | 213 | 214 | # Assemble the File Event API call with token authorization 215 | $resourceurl = '/forensic-search/queryservice/api/v1/fileevent/' 216 | $uri = $FullCPServerAddress + $resourceurl 217 | 218 | $headers = @{} 219 | $headers.Add("Authorization","v3_user_token " + $v3Token) 220 | Write-Host "Executing search..." 221 | 222 | # Execute the search and measure the search response 223 | $time = Measure-Command { 224 | $searchRequest = invoke-WebRequest -Uri $uri -Method POST -Body $json -ContentType $ContentType -Headers $headers 225 | } 226 | Write-Host "Done!" 227 | Write-Host "Total search time: " $time.Milliseconds " milliseconds" 228 | 229 | $searchResultBody = $searchRequest | ConvertFrom-Json 230 | 231 | # Output the results and record the # of results and search in files 232 | $timeStamp = (get-date -f yyyy-MM-dd_hh_mm_ss) 233 | $pathToCsvOutputFile = "ffs_results_" + $timeStamp + ".csv" 234 | $pathToTxtOutputFile = "ffs_search_" + $timeStamp + ".txt" 235 | $searchResultBody | Select-Object -expand fileEvents | 236 | ConvertTo-Csv -NoTypeInformation | 237 | Set-Content $pathToCsvOutputFile 238 | 239 | "Total Results: " + $searchResultBody.totalCount | Set-Content $pathToTxtOutputFile 240 | "Search Query: " | Add-Content $pathToTxtOutputFile 241 | $json | Add-Content $pathToTxtOutputFile 242 | 243 | # Final result count display 244 | Write-Host "Results: " $searchResultBody.totalCount 245 | Write-Host "See file" $pathToCsvOutputFile "for results." 246 | # Wait for a key to continue 247 | & cmd /c pause 248 | Exit 249 | -------------------------------------------------------------------------------- /c42SharedLibScripts/legacy_pre_4.2/ExportAllUserDataCreationDate.py: -------------------------------------------------------------------------------- 1 | # By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no cost separately from Code42's commercial offerings. 2 | # This software is not provided under Code42's master services agreement. 3 | # It is provided AS-IS, without support, and subject to the license below. 4 | # Any support and documentation for this software are available at the Code42 community site. 5 | 6 | # The MIT License (MIT) 7 | # Copyright (c) 2019 Code42 8 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 10 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 11 | # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 12 | # is furnished to do so, subject to the following conditions: 13 | 14 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 17 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | # File: ExportAllUserDataCreationDate.py 22 | # Author: AJ LaVenture, Code 42 Software 23 | # Last Modified: 08-20-2013 24 | # 25 | # Exports all user data with the ability to choose metadata object to query on 26 | # only supports creation date as of right now with date range (start, end) 27 | # will take json data and covert it to CSV file 28 | # 29 | # creationDate 30 | # 31 | # Log file: ExportAllUserDataCreationDate.py 32 | # 33 | # Python 2.7 34 | # REQUIRED MODULE: Requests 35 | # http://docs.python-requests.org/en/latest/user/install/ 36 | # 37 | # Uses relativedelta python module that can be downloaded from: 38 | # http://labix.org/python-dateutil 39 | # 40 | # API Call: GET api/User 41 | # 42 | # Arguments: startMonth, monthsForward(default 1), loggingLevel(optional) 43 | # 44 | # Example Usages: 45 | # 1) Export user data with start date and how many months in the future 46 | # month 8, for 1 month 47 | # python ExportAllUserDataCreationDate.py 8 1 48 | # 49 | # 2) Export user data based on date range with debug log level 50 | # python ExportAllUserDataCreationDate.py 8/1/2013 1 DEBUG 51 | # 52 | # from datetime import datetime 53 | # 54 | # date_object = datetime.strptime('Jun 1 2005 1:33PM', '%b %d %Y %I:%M%p') 55 | # 56 | # http://docs.python.org/library/datetime.html#datetime.datetime.strptime 57 | # http://docs.python.org/library/datetime.html#strftime-and-strptime-behavior 58 | 59 | 60 | from c42SharedLibrary import c42Lib 61 | import sys 62 | import json 63 | import csv 64 | import base64 65 | import logging 66 | import requests 67 | import math 68 | from dateutil.relativedelta import * 69 | import datetime 70 | import calendar 71 | import getpass 72 | 73 | 74 | # Set to your environments values 75 | #cp_host = "" ex: http://localhost or https://localhost 76 | #cp_port = "" ex: 4280 or 4285 77 | #cp_username = "" 78 | #cp_password = "" 79 | 80 | # Test values 81 | c42Lib.cp_host = "http://aj-proappliance" 82 | c42Lib.cp_port = "4280" 83 | c42Lib.cp_username = "admin" 84 | c42Lib.cp_password = getpass.getpass('Enter your CrashPlan console password: ') # You will be prompted for your password 85 | 86 | 87 | c42Lib.cp_logLevel = "INFO" 88 | c42Lib.cp_logFileName = "ExportAllUserDataCreationDate.log" 89 | c42Lib.setLoggingLevel() 90 | 91 | csvUserHeaders = ['userId','username','email','orgId','orgName','computerCount','creationDate','UserArchive-oldestDate','UserArchive-oldestDateBytes','oldestHR','UserArchive-newestDate','UserArchive-newestDateBytes','newestHR','UserArchiveBytesDeltaMonth','deltaHR'] 92 | 93 | ## ARGUMENTS ## 94 | # ARG1 - start date for query (String) 95 | cp_startDate = str(sys.argv[1]) 96 | 97 | # ARG2 - end date for query (String) 98 | cp_deltaDate = 1 99 | if len(sys.argv)==3: 100 | cp_deltaDate = str(sys.argv[2]) 101 | 102 | # ARG3 (optional)- logging level for console (default is INFO, add DEBUG for additional output to console) 103 | cp_logLevel = "INFO" 104 | if len(sys.argv)==4: 105 | cp_logLevel = str(sys.argv[3]) 106 | 107 | 108 | def getUserReport(): 109 | logging.info("User Export Action: startDate:" + cp_startDate + " | deltaTime: " + 110 | str(cp_deltaDate) + " | loggingLevel: " + cp_logLevel) 111 | csvUserFile = csv.writer(open("ExportAllUserDataCreateDateReport.csv", "wb+")) 112 | csvUserFile.writerow(csvUserHeaders) 113 | 114 | start_date_object = "" 115 | # end_date_object = "" 116 | 117 | start_date_object = datetime.datetime.strptime(cp_startDate, '%m/%d/%Y') 118 | # end_date_object = datetime.datetime.strptime(cp_endDate, '%m/%d/%Y') 119 | # print end_date_object 120 | 121 | emptyBlock = "" 122 | 123 | users = c42Lib.getAllUsers() 124 | # users = c42Lib.getUsers(1) 125 | for index, user in enumerate(users): 126 | # user = c42Lib.getUserById('6031') 127 | # user = c42Lib.getUserById('7403') 128 | 129 | printRow = [] 130 | 131 | creationDate = "" 132 | if 'creationDate' in user: 133 | creationDate = user['creationDate'] 134 | 135 | currDateObj = "" 136 | deltaTime = "" 137 | 138 | if creationDate is not None: 139 | currDateObj = datetime.datetime.strptime(str(creationDate)[:10], "%Y-%m-%d") 140 | deltaTime = start_date_object+relativedelta(months=+int(cp_deltaDate)) 141 | 142 | 143 | # logging.info(str(currDateObj) + " delta: " + str(deltaTime)) 144 | if deltaTime > currDateObj > start_date_object: 145 | 146 | pUserId = user['userId'] 147 | # logging.info(str(pUserId)) 148 | printRow.extend([str(pUserId)]) 149 | 150 | pUserName = user['username'] 151 | printRow.extend([pUserName.encode('utf-8')]) 152 | 153 | pUserEmail = user['email'] 154 | if pUserEmail is not None: 155 | printRow.extend([pUserEmail.encode('utf-8')]) 156 | else: 157 | printRow.extend([emptyBlock]) 158 | 159 | pUserOrgId = user['orgId'] 160 | printRow.extend([str(pUserOrgId)]) 161 | 162 | pUserOrgName = user['orgName'] 163 | printRow.extend([pUserOrgName.encode('utf-8')]) 164 | 165 | if 'computerCount' in user: 166 | pUserComputerCount = user['computerCount'] 167 | printRow.extend([str(pUserComputerCount)]) 168 | else: 169 | printRow.extend([emptyBlock]) 170 | 171 | if 'creationDate' in user: 172 | pUserCreationDate = user['creationDate'] 173 | printRow.extend([pUserCreationDate]) 174 | 175 | if 'backupUsage' in user: 176 | 177 | userBackupUsage = user['backupUsage'] 178 | 179 | if userBackupUsage: 180 | 181 | if 'archiveHistory' in userBackupUsage[0]: 182 | 183 | userBackupUsageArchiveHistory = userBackupUsage[0]['archiveHistory'] 184 | 185 | if userBackupUsageArchiveHistory: 186 | 187 | oldestDate = 9999999999999999999 188 | # "20130801": 1909986568291, 189 | oldestDateBytes = 0 190 | newestDate = 0 191 | # "20130830": 1932985363857 192 | newestDateBytes = 0 193 | 194 | for key, value in userBackupUsageArchiveHistory.items(): 195 | # print key, value 196 | # print oldestDate, int(key) 197 | if (oldestDate > int(key)): 198 | oldestDate = int(key) 199 | oldestDateBytes = value 200 | 201 | # print newestDate, int(key) 202 | if (newestDate <= int(key)): 203 | newestDate = int(key) 204 | newestDateBytes = value 205 | 206 | printRow.extend([str(oldestDate)]) 207 | printRow.extend([str(oldestDateBytes)]) 208 | printRow.extend([c42Lib.sizeof_fmt(oldestDateBytes)]) 209 | 210 | printRow.extend([str(newestDate)]) 211 | printRow.extend([str(newestDateBytes)]) 212 | printRow.extend([c42Lib.sizeof_fmt(newestDateBytes)]) 213 | 214 | 215 | pArchiveBytesDeltaMonth = userBackupUsage[0]['archiveBytesDeltaMonth'] 216 | printRow.extend([str(pArchiveBytesDeltaMonth)]) 217 | printRow.extend([c42Lib.sizeof_fmt(pArchiveBytesDeltaMonth)]) 218 | 219 | 220 | # pArchiveBytesDeltaMonthSanityCheck = newestDateBytes - oldestDateBytes 221 | # printRow.extend([str(pArchiveBytesDeltaMonthSanityCheck)]) 222 | # printRow.extend([c42Lib.sizeof_fmt(pArchiveBytesDeltaMonthSanityCheck)]) 223 | 224 | 225 | # else: 226 | # printUserEmptyBlock() 227 | 228 | # else: 229 | # printUserEmptyBlock() 230 | 231 | # else: 232 | # printUserEmptyBlock() 233 | 234 | logging.info(str(printRow)) 235 | csvUserFile.writerow(printRow) 236 | else: 237 | logging.debug("nothing to report") 238 | # print printRow 239 | 240 | 241 | def performUserExportAction(): 242 | logging.info("User Export Action: startDate:" + cp_startDate + " | deltaTime: " + 243 | str(cp_deltaDate) + " | loggingLevel: " + cp_logLevel) 244 | 245 | 246 | # print cp_startDate + " " + cp_endDate + " " + cp_logLevel 247 | start_date_object = "" 248 | # end_date_object = "" 249 | 250 | start_date_object = datetime.datetime.strptime(cp_startDate, '%m/%d/%Y') 251 | # end_date_object = datetime.datetime.strptime(cp_endDate, '%m/%d/%Y') 252 | # print end_date_object 253 | 254 | 255 | count = 0 256 | numRequests = getUsersPageCount() 257 | firstUsers = getUsers(1) 258 | 259 | csvFile = csv.writer(open("userCreateDate.csv", "wb+")) 260 | 261 | #write CSV Header 262 | totalCount = firstUsers['totalCount'] 263 | columns = [] 264 | # print totalCount 265 | if totalCount > 1: 266 | userListObject = firstUsers['users'] 267 | for index, users in enumerate(userListObject): 268 | # print index, user 269 | # skip admin user 270 | # print users['userId'] 271 | if index == 1 and users['userId'] != 1: 272 | # print index 273 | currUserObject = userListObject[index] 274 | for key, value in currUserObject.iteritems(): 275 | # print key, value 276 | # columnName = key.replace("u'","") 277 | # print key 278 | columns.extend([key]) 279 | #print users 280 | # print columns 281 | csvFile.writerow(columns) 282 | for x in xrange(1, numRequests+1): 283 | users = getUsers(str(x)) 284 | userListObject = users['users'] 285 | for index, users in enumerate(userListObject): 286 | currValues = [] 287 | currUserObject = userListObject[index] 288 | # skip admin user 289 | if users['userId'] != 1: 290 | writeTocsv = "true" 291 | for key, value in currUserObject.iteritems(): 292 | if value is None: 293 | value = "null" 294 | #custom reporting filter here 295 | if key == "creationDate": 296 | currDateObj = datetime.datetime.strptime(str(value)[:10], "%Y-%m-%d") 297 | deltaTime = start_date_object+relativedelta(months=+int(cp_deltaDate)) 298 | if deltaTime > currDateObj: 299 | writeTocsv = "true" 300 | logging.info("index: "+ str(index) +" | creationDate - " + str(currDateObj)) 301 | else: 302 | writeTocsv = "false" 303 | currValues.extend([value]) 304 | if writeTocsv == "true": 305 | # print writeTocsv 306 | csvFile.writerow(currValues) 307 | logging.info(currValues) 308 | 309 | 310 | 311 | 312 | getUserReport() 313 | -------------------------------------------------------------------------------- /c42SharedLibScripts/legacy_pre_4.2/allUserAndDeviceReport.py: -------------------------------------------------------------------------------- 1 | # By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no cost separately from Code42's commercial offerings. 2 | # This software is not provided under Code42's master services agreement. 3 | # It is provided AS-IS, without support, and subject to the license below. 4 | # Any support and documentation for this software are available at the Code42 community site. 5 | 6 | # The MIT License (MIT) 7 | # Copyright (c) 2019 Code42 8 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 10 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 11 | # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 12 | # is furnished to do so, subject to the following conditions: 13 | 14 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 17 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | # 22 | # File: allUserAndDeviceReport.py 23 | # Author: AJ LaVenture, Code 42 Software 24 | # Last Modified: 09-04-2013 25 | # 26 | # get report data on all users and devices 27 | # 28 | 29 | from c42SharedLibrary import c42Lib 30 | import math 31 | import sys 32 | import json 33 | import csv 34 | import base64 35 | import logging 36 | import requests 37 | import math 38 | from dateutil.relativedelta import * 39 | import datetime 40 | import calendar 41 | import getpass 42 | 43 | c42Lib.cp_host = "http://aj-proappliance" 44 | c42Lib.cp_port = "4280" 45 | c42Lib.cp_username = "admin" 46 | c42Lib.cp_password = getpass.getpass('Enter your CrashPlan console password: ') # You will be prompted for your password 47 | 48 | c42Lib.cp_logLevel = "INFO" 49 | c42Lib.cp_logFileName = "allUserAndDeviceReport.log" 50 | c42Lib.setLoggingLevel() 51 | 52 | # c42Lib.MAX_PAGE_NUM = 5 53 | 54 | # cp_api_storedBytesHistory = "/api/storedBytesHistory" 55 | 56 | 57 | 58 | csvUserHeaders = ['userId','username','email','orgId','orgName','computerCount','UserArchive-oldestDate','UserArchive-oldestDateBytes','oldestHR','UserArchive-newestDate','UserArchive-newestDateBytes','newestHR','UserArchiveBytesDeltaMonth','deltaHR'] 59 | 60 | emptyBlock = "" 61 | 62 | csvDeviceHeader = ['userId','computerId','computerName','computerType','osName','computerStatus','storePointId','storePointName','serverId','serverName', 63 | 'selectedFiles','selectedBytes','selectedBytesHR','archiveBytes','archiveBytesHR','percentComplete','lastCompletedBackup', 64 | 'CompArchive-oldestDate','CompArchive-oldestDateBytes','oldestHR','CompArchive-newestDate','CompArchive-newestDateBytes','newsetHR','CompArchive-Delta','deltaHR'] 65 | printValues = [] 66 | printRow = [] 67 | 68 | 69 | def getDeviceReport(): 70 | csvDeviceFile = csv.writer(open("allDeviceReport.csv", "wb+")) 71 | csvDeviceFile.writerow(csvDeviceHeader) 72 | 73 | devices = c42Lib.getAllDevices() 74 | # devices = c42Lib.getDevices(1) 75 | 76 | for index, device in enumerate(devices): 77 | # device = c42Lib.getDeviceById(8637) 78 | printValues = [] 79 | 80 | # computerId = loopDevice['computerId'] 81 | 82 | # device = c42Lib.getDeviceById(computerId) 83 | 84 | userId = device['userId'] 85 | printValues.extend([str(userId)]) 86 | 87 | # print deviceObject 88 | computerId = device['computerId'] 89 | printValues.extend([str(computerId)]) 90 | 91 | computerName = device['name'] 92 | printValues.extend([computerName.encode('utf-8')]) 93 | 94 | computerType = device['type'] 95 | printValues.extend([computerType.encode('utf-8')]) 96 | 97 | osName = device['osName'] 98 | printValues.extend([osName.encode('utf-8')]) 99 | 100 | computerStatus = device['status'] 101 | printValues.extend([computerStatus.encode('utf-8')]) 102 | 103 | 104 | if 'backupUsage' in device: 105 | 106 | deviceObjectBackupUsage = device['backupUsage'] 107 | 108 | for backupUsage in deviceObjectBackupUsage: 109 | 110 | #only if SOM-backup 111 | if backupUsage['targetComputerId'] == 2: 112 | 113 | storePointId = backupUsage['storePointId'] 114 | printValues.extend([str(storePointId)]) 115 | 116 | storePointName = backupUsage['storePointName'] 117 | printValues.extend([storePointName.encode('utf-8')]) 118 | 119 | serverId = backupUsage['serverId'] 120 | printValues.extend([str(serverId)]) 121 | 122 | serverName = backupUsage['serverName'] 123 | printValues.extend([serverName.encode('utf-8')]) 124 | 125 | selectedFiles = backupUsage['selectedFiles'] 126 | printValues.extend([str(selectedFiles)]) 127 | 128 | selectedBytes = backupUsage['selectedBytes'] 129 | printValues.extend([str(selectedBytes)]) 130 | 131 | printValues.extend([c42Lib.sizeof_fmt(selectedBytes)]) 132 | 133 | archiveBytes = backupUsage['archiveBytes'] 134 | printValues.extend([str(archiveBytes)]) 135 | 136 | printValues.extend([c42Lib.sizeof_fmt(archiveBytes)]) 137 | 138 | percentComplete = backupUsage['percentComplete'] 139 | printValues.extend([str(percentComplete)]) 140 | 141 | lastCompletedBackup = backupUsage['lastCompletedBackup'] 142 | printValues.extend([str(lastCompletedBackup)]) 143 | 144 | 145 | if 'history' in backupUsage: 146 | 147 | backupHistory = backupUsage['history'] 148 | 149 | if backupHistory: 150 | 151 | oldestDate = 9999999999999999999 152 | # "20130801": 1909986568291, 153 | oldestDateBytes = 0 154 | newestDate = 0 155 | # "20130830": 1932985363857 156 | newestDateBytes = 0 157 | 158 | for singleHistory in backupHistory: 159 | # print key, value 160 | # print oldestDate, int(key) 161 | currDate = singleHistory['date'].replace('-','') 162 | 163 | currarchiveBytes = singleHistory['archiveBytes'] 164 | 165 | if (oldestDate > int(currDate)): 166 | oldestDate = int(currDate) 167 | oldestDateBytes = currarchiveBytes 168 | 169 | # print newestDate, int(key) 170 | if (newestDate <= int(currDate)): 171 | newestDate = int(currDate) 172 | newestDateBytes = currarchiveBytes 173 | 174 | printValues.extend([str(oldestDate)]) 175 | printValues.extend([str(oldestDateBytes)]) 176 | printValues.extend([c42Lib.sizeof_fmt(oldestDateBytes)]) 177 | 178 | printValues.extend([str(newestDate)]) 179 | printValues.extend([str(newestDateBytes)]) 180 | printValues.extend([c42Lib.sizeof_fmt(newestDateBytes)]) 181 | 182 | pArchiveBytesDeltaMonth = newestDateBytes - oldestDateBytes 183 | printValues.extend([str(pArchiveBytesDeltaMonth)]) 184 | printValues.extend([c42Lib.sizeof_fmt(pArchiveBytesDeltaMonth)]) 185 | # else: 186 | # printDeviceEmptyBlock(8) 187 | # else: 188 | # printDeviceEmptyBlock(8) 189 | # else: 190 | # print("hi") 191 | # printDeviceEmptyBlock(19) 192 | 193 | # else: 194 | # print("bye") 195 | # printDeviceEmptyBlock(19) 196 | 197 | # print ', '.join(printValues) 198 | logging.info(str(printValues)) 199 | csvDeviceFile.writerow(printValues) 200 | 201 | 202 | 203 | def printDeviceEmptyBlock(max): 204 | for x in range(int(max)): 205 | printValues.extend([emptyBlock]) 206 | 207 | def printUserEmptyBlock(): 208 | printRow.extend([emptyBlock]) 209 | printRow.extend([emptyBlock]) 210 | printRow.extend([emptyBlock]) 211 | printRow.extend([emptyBlock]) 212 | # printRow.extend([emptyBlock]) 213 | # printRow.extend([emptyBlock]) 214 | 215 | 216 | 217 | def getUserReport(): 218 | csvUserFile = csv.writer(open("allUserReport.csv", "wb+")) 219 | csvUserFile.writerow(csvUserHeaders) 220 | 221 | users = c42Lib.getAllUsers() 222 | for index, user in enumerate(users): 223 | # user = c42Lib.getUserById('6031') 224 | # user = c42Lib.getUserById('7403') 225 | 226 | printRow = [] 227 | 228 | pUserId = user['userId'] 229 | printRow.extend([str(pUserId)]) 230 | 231 | pUserName = user['username'] 232 | printRow.extend([pUserName.encode('utf-8')]) 233 | 234 | pUserEmail = user['email'] 235 | if pUserEmail is not None: 236 | printRow.extend([pUserEmail.encode('utf-8')]) 237 | else: 238 | printRow.extend([emptyBlock]) 239 | 240 | pUserOrgId = user['orgId'] 241 | printRow.extend([str(pUserOrgId)]) 242 | 243 | pUserOrgName = user['orgName'] 244 | printRow.extend([pUserOrgName.encode('utf-8')]) 245 | 246 | if 'computerCount' in user: 247 | pUserComputerCount = user['computerCount'] 248 | printRow.extend([str(pUserComputerCount)]) 249 | else: 250 | printRow.extend([emptyBlock]) 251 | 252 | 253 | if 'backupUsage' in user: 254 | 255 | userBackupUsage = user['backupUsage'] 256 | 257 | if userBackupUsage: 258 | 259 | if 'archiveHistory' in userBackupUsage[0]: 260 | 261 | userBackupUsageArchiveHistory = userBackupUsage[0]['archiveHistory'] 262 | 263 | if userBackupUsageArchiveHistory: 264 | 265 | oldestDate = 9999999999999999999 266 | # "20130801": 1909986568291, 267 | oldestDateBytes = 0 268 | newestDate = 0 269 | # "20130830": 1932985363857 270 | newestDateBytes = 0 271 | 272 | for key, value in userBackupUsageArchiveHistory.items(): 273 | # print key, value 274 | # print oldestDate, int(key) 275 | if (oldestDate > int(key)): 276 | oldestDate = int(key) 277 | oldestDateBytes = value 278 | 279 | # print newestDate, int(key) 280 | if (newestDate <= int(key)): 281 | newestDate = int(key) 282 | newestDateBytes = value 283 | 284 | printRow.extend([str(oldestDate)]) 285 | printRow.extend([str(oldestDateBytes)]) 286 | printRow.extend([c42Lib.sizeof_fmt(oldestDateBytes)]) 287 | 288 | printRow.extend([str(newestDate)]) 289 | printRow.extend([str(newestDateBytes)]) 290 | printRow.extend([c42Lib.sizeof_fmt(newestDateBytes)]) 291 | 292 | 293 | pArchiveBytesDeltaMonth = userBackupUsage[0]['archiveBytesDeltaMonth'] 294 | printRow.extend([str(pArchiveBytesDeltaMonth)]) 295 | printRow.extend([c42Lib.sizeof_fmt(pArchiveBytesDeltaMonth)]) 296 | 297 | 298 | # pArchiveBytesDeltaMonthSanityCheck = newestDateBytes - oldestDateBytes 299 | # printRow.extend([str(pArchiveBytesDeltaMonthSanityCheck)]) 300 | # printRow.extend([c42Lib.sizeof_fmt(pArchiveBytesDeltaMonthSanityCheck)]) 301 | 302 | 303 | else: 304 | printUserEmptyBlock() 305 | 306 | else: 307 | printUserEmptyBlock() 308 | 309 | else: 310 | printUserEmptyBlock() 311 | 312 | logging.info(str(printRow)) 313 | csvUserFile.writerow(printRow) 314 | # print printRow 315 | 316 | 317 | def printUserEmptyBlock(): 318 | printRow.extend([emptyBlock]) 319 | printRow.extend([emptyBlock]) 320 | printRow.extend([emptyBlock]) 321 | printRow.extend([emptyBlock]) 322 | # printRow.extend([emptyBlock]) 323 | # printRow.extend([emptyBlock]) 324 | 325 | 326 | 327 | getUserReport() 328 | getDeviceReport() 329 | # print str(c42Lib.getDevicesPageCount()) 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | -------------------------------------------------------------------------------- /pushRestore.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ####################################################################################### 3 | # 4 | # Edit the variables near the top of this script then run it! 5 | # This script is fire and forget, progress is not monitored with this script. That must be done via client or server logs. 6 | # Usage: ./pushRestore.sh 7 | # 8 | # This example script performs an automated Push Restore (via REST): 9 | # * One or more source files/directories. 10 | # * To local or remote destination computer. To remote directory of choice. 11 | # * Using MPC or Cloud storage. 12 | # * Recursive restore (includes subdirectories). 13 | # * Between computers of different users (if Admin). 14 | # * Supports restoring to original location 15 | # * ...and more. 16 | # 17 | # 18 | # Notes: 19 | # * Push destination must be running authenticated Code42 client. 20 | # * Archive adoption (or original owner) not required. 21 | # 22 | # Author: Code 42 Software 23 | # Modified by Jack Phinney, Code42 Software, 9/19/2016 24 | # Modified by Bryan Coe, Code42 Software, 3/7/2017 25 | # Modified by Evan Niessen-Derry, Code42 Software, 3/23/2017 26 | # Modified by Andrew Orrison, Code42 Software, 12/10/2018 27 | # 28 | # -------- 29 | # 30 | # # Copyright (c) 2017 Code42, Inc. 31 | # Permission is hereby granted, free of charge, to any person obtaining a copy 32 | # of this software and associated documentation files (the "Software"), to deal 33 | # in the Software without restriction, including without limitation the rights 34 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 35 | # copies of the Software, and to permit persons to whom the Software is 36 | # furnished to do so, subject to the following conditions: 37 | # The above copyright notice and this permission notice shall be included in all 38 | # copies or substantial portions of the Software. 39 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 40 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 41 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 42 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 43 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 44 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 45 | # SOFTWARE. 46 | ####################################################################################### 47 | 48 | 49 | ################################### 50 | # Variables (customize before running) 51 | ################################### 52 | 53 | # PROe Master Server (e.g. http/4280 or https/4285) 54 | MASTER='' 55 | 56 | # Restore credentials 57 | CPUSER='' 58 | read -r -s -p Password: CPPASS 59 | CPLOGIN="$CPUSER:$CPPASS" 60 | echo "" 61 | 62 | # Source computer 63 | sourceComputerGuid= 64 | 65 | # Accepting computer 66 | acceptingComputerGuid= 67 | 68 | # Add any number of Files or Directories, no need to be specific. 69 | # "C:/" or "/" will restore everything that was backed up from the main drive 70 | # Note that Windows systems need "C:/" or other drive letter prepended 71 | getDirs=( 72 | "C:/" 73 | ) 74 | # Example getFiles array. If you'd like to restore only particular files add 75 | # them one per line, quoted, in the getFiles array 76 | #getFiles=( 77 | # "/Users/example/Desktop/test.pdf" 78 | # "C:/Users/example/Desktop/test.pdf" 79 | #) 80 | getFiles=() 81 | 82 | #Backup date for the restore 83 | #Default date is most recent/when the script is run. That is calculated with: $(date +%Y%m%d) 84 | #Enter date as 'YYYYMMDD' to override 85 | restoreDate='' 86 | 87 | # Where on accepting computer to restore 88 | restorePath='C:/pushrestore/' 89 | 90 | L1SEC='AccountPassword' 91 | L2SEC='PrivatePassword' 92 | L3SEC='CustomKey' 93 | 94 | #Original Location Support 95 | #May be "ORIGINAL_LOCATION" or "TARGET_DIRECTORY" using target directory takes into account the restorePath 96 | pushRestoreStrategy="TARGET_DIRECTORY" 97 | #true or false. default value is "false", 98 | permitRestoreToDifferentOsVersion=false 99 | #May be "OVERWRITE_ORIGINAL" or "RENAME_ORIGINAL" 100 | existingFiles="RENAME_ORIGINAL" 101 | #May be "CURRENT" or "ORIGINAL" 102 | filePermissions="CURRENT" 103 | ################################### 104 | # Helper Functions 105 | ################################### 106 | 107 | # API Get 108 | function api { 109 | curl -sku "$CPLOGIN" --header "Accept: application/json" "$MASTER/api/${1}?${2}" 110 | } 111 | 112 | # API Post 113 | function apiPost { 114 | curl -sku "$CPLOGIN" --header "Content-Type: application/json" -X POST -d "${2}" "$MASTER/api/${1}" 115 | } 116 | 117 | # Json value for given key 118 | function jsonValue { 119 | echo "${2}" | grep -o "\"${1}\":\"[^\"]*\"" | sed 's/^.*"'"${1}"'":"\([^"]*\)".*$/\1/' 120 | } 121 | 122 | 123 | ################################### 124 | # Auth Tester 125 | ################################### 126 | #Basic check to see if we can authenticate with the server 127 | #This is to prevent account lockout by successive api call failures due to a bad password 128 | #Generic. On error this will output the "description" value 129 | # JSON=$(api Computer 'q="'${sourceComputerGuid}'"') 130 | JSON=$(api Computer/"${sourceComputerGuid}" 'idType=guid&active=true') 131 | if [[ ${sourceComputerGuid} != $(jsonValue guid "${JSON}") ]]; then 132 | echo Error: "$(jsonValue description "${JSON}")" 133 | exit 2 134 | fi 135 | 136 | userUID=$(jsonValue userUid "$JSON") 137 | JSON=$(api "User/$userUID" "idType=uid&incSecurityKeyType=true") 138 | securityLevel=$(jsonValue securityKeyType "$JSON") 139 | 140 | ################################### 141 | # Setup Session 142 | ################################### 143 | 144 | #Perform basic sanity checks on restoreDate 145 | #Set to now if left empty 146 | if [[ ${restoreDate} == '' ]]; then 147 | restoreDate=$(date +%Y%m%d) 148 | elif [[ ${restoreDate} =~ ^-?[0-9]+$ ]]; then 149 | if [[ "$restoreDate" -gt $(date +%Y%m%d) ]] || [[ "$restoreDate" -lt 20020507 ]]; then 150 | echo "Error: date of ${restoreDate} is not within valid range" 151 | exit 1 152 | fi 153 | else 154 | echo "Error: \"${restoreDate}\" is not a valid date" 155 | exit 1 156 | fi 157 | #convert restoreDate to epoch date with ms (needed for api) 158 | if [[ "$(uname)" = 'Darwin' ]]; then 159 | restoreDateEpoch=$(date -j -f %Y%m%d "$restoreDate" +%s) 160 | else 161 | restoreDateEpoch=$(date -d "$restoreDate" +'%s') 162 | fi 163 | restoreDateEpochMS=$(( restoreDateEpoch * 1000 )) 164 | 165 | echo -e "Setting up Session...\n" 166 | 167 | # Get source computer info 168 | JSON=$(api Computer/"${sourceComputerGuid}" 'idType=guid&incBackupUsage=true&active=true') 169 | sourceComputerName=$(jsonValue name "${JSON}") 170 | 171 | # Get backup destination 172 | # If more than one, prompt user to select 173 | IFS=$'\n' 174 | backupDestArray=($(jsonValue serverGuid "${JSON}")) 175 | backupDestCount=${#backupDestArray[@]} 176 | backupDestChosen=false # this is just a string, but substitutes alright for a boolean 177 | 178 | if [[ ${backupDestCount} == 1 ]]; then 179 | backupServerGuid=${backupDestArray[0]} 180 | backupDestName=$(jsonValue serverName "${JSON}") 181 | elif [[ ${backupDestCount} -gt 1 ]]; then 182 | backupDestArray+=($(jsonValue serverName "${JSON}")) 183 | echo -e "Multiple backup destinations found!\n" 184 | 185 | while [[ $backupDestChosen == false ]]; do 186 | for (( i=0; i 0) 142 | while params['pgNum'] <= pagesNeeded: 143 | response = genericRequest('get', '/api/user', params=params, payload={}) 144 | content = response.text 145 | data = json.loads(content)['data'] 146 | 147 | allUsers.extend(data['users']) 148 | params['pgNum'] += 1 149 | 150 | return allUsers 151 | 152 | def usernameIsEmail(username): 153 | try: 154 | usernameTest = username.split("@") 155 | if len(usernameTest) == 2: 156 | return True 157 | else: 158 | return False 159 | except: 160 | return False 161 | 162 | def checkIfUsernamesAreEmailAddresses(allUsers): 163 | if os.path.exists("usernamesNotemailAddresses.csv"): 164 | os.remove("usernamesNotemailAddresses.csv") 165 | notCorrectUsers = [] 166 | 167 | with open("usernamesNotemailAddresses.csv", "a") as text_file: 168 | content = "UserId,Username,Active\n" 169 | text_file.write(content) 170 | text_file.close() 171 | 172 | for eachUser in allUsers: 173 | if not usernameIsEmail(eachUser['username']): 174 | notCorrectUsers.append(eachUser) 175 | content = str(eachUser['userId'])+","+ eachUser['username']+","+str(eachUser['active'])+"\n" 176 | with open("usernamesNotemailAddresses.csv", "a") as text_file: 177 | text_file.write(content) 178 | text_file.close() 179 | 180 | testServerConnectivity() 181 | testCredentials() 182 | 183 | allUsers = getAllUsers() 184 | 185 | dfAllUsersToProcess = pd.DataFrame(allUsers,columns=['userId','userUid','active','username','email','firstName','lastName']) 186 | dfAllUsersToProcess['Old Username'] = dfAllUsersToProcess['username'] 187 | 188 | if method == 1: 189 | dfAllUsersToProcess['username'] = dfAllUsersToProcess['email'] 190 | elif method ==2: 191 | dfAllUsersToProcess['username'] = dfAllUsersToProcess['username'] + domain 192 | elif method == 3: 193 | dfAllUsersToProcess['username'] = dfAllUsersToProcess['firstName'] + '.' + dfAllUsersToProcess['lastName'] + domain 194 | elif method == 4: 195 | checkIfUsernamesAreEmailAddresses(allUsers) 196 | print ("Users without email addresses have been printed to usernamesNotEmailAddresses.csv") 197 | sys.exit() 198 | 199 | #dfAllUsersToProcess = dfAllUsersToProcess.fillna('empty') 200 | 201 | if args.inputFile: 202 | lines = filename.read().splitlines() 203 | dfAllUsersToProcess = dfAllUsersToProcess[dfAllUsersToProcess['userId'].isin(lines)] 204 | else: 205 | print ("Processing all users." ) 206 | #print (dfAllUsersToProcess.to_string() ) 207 | #Check to see if there are any users that don't have an @ symbol in their username. 208 | #If this is the case something is not right, and needs to be fixed. 209 | #If this is the case something is not right, and needs to be fixed. 210 | 211 | allResults = [] 212 | for index, row in dfAllUsersToProcess.iterrows(): 213 | result = {} 214 | newUsername = row['username'] 215 | oldUsername = row['Old Username'] 216 | userId = row['userId'] 217 | result['User Id'] = userId 218 | result['New Username'] = newUsername 219 | result['Old Username'] = oldUsername 220 | 221 | call = '/api/user/' + str(userId) 222 | toSend = {} 223 | toSend['email'] = newUsername 224 | toSend['username'] = newUsername 225 | payload = json.dumps(toSend) 226 | attemptStatus="" 227 | singleQuote='\'' 228 | if usernameIsEmail(newUsername): 229 | if usernameIsEmail(oldUsername): 230 | print("Did not change", oldUsername + " to ", newUsername, "it is already an email address.") 231 | attemptStatus = ", username is already an email address" 232 | elif newUsername == oldUsername: 233 | print ("Did not change", oldUsername + " to ", newUsername, "it is already the same.") 234 | attemptStatus = ", already Set to Username" 235 | elif singleQuote in newUsername : 236 | print ("Did not change",oldUsername+" to ",newUsername,"it has a single quote in it. Please manually change username to an email address.") 237 | attemptStatus=", ' in email address." 238 | elif not username==oldUsername: 239 | response = genericRequest('put', call, params={}, payload=payload) 240 | else: 241 | print ("Did not change",oldUsername+" to ",newUsername,"This is the user running this script.") 242 | attemptStatus= ", username used for script" 243 | else: 244 | print("Did not change", oldUsername + " to ", newUsername, "It is not a valid email address.") 245 | attemptStatus = ", new username not a valid email address" 246 | try: 247 | if (response.status_code != 200): 248 | print (response.text ) 249 | result['Status'] = 'Failure' 250 | print (payload ) 251 | else: 252 | print ("Changed",oldUsername+" to",newUsername ) 253 | result['Status'] = 'Success' 254 | except : 255 | if not execute and attemptStatus == "": 256 | result['Status'] = 'Dry run Success' 257 | else: 258 | result['Status'] = 'Failure'+attemptStatus 259 | 260 | try: 261 | print (response.text ) 262 | except: 263 | None 264 | 265 | allResults.append(result) 266 | dfAllResults = pd.DataFrame(allResults,columns=['User Id','New Username','Old Username','Status']) 267 | failures = dfAllResults.shape[0]-dfAllResults[dfAllResults.Status == 'Success'].shape[0] 268 | if execute: 269 | print ("Completed. \nUsers changed:",dfAllResults[dfAllResults.Status == 'Success'].shape[0],"Users with an error:",failures ) 270 | else: 271 | print ("This was a dry run. Use the -e flag to run for real.") 272 | 273 | #print (dfAllResults.to_string()) 274 | dfAllResults.to_csv('UsernamesChangedResults-'+startTime+'.csv', encoding='utf-8', index=False) 275 | -------------------------------------------------------------------------------- /deactivateDevices.py: -------------------------------------------------------------------------------- 1 | # By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no cost separately from Code42's commercial offerings. 2 | # This software is not provided under Code42's master services agreement. 3 | # It is provided AS-IS, without support, and subject to the license below. 4 | # Any support and documentation for this software are available at the Code42 community site. 5 | 6 | # The MIT License (MIT) 7 | # Copyright (c) 2019 Code42 8 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 10 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 11 | # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 12 | # is furnished to do so, subject to the following conditions: 13 | 14 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 17 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | # 22 | # File: deactivateDevices.py 23 | # Author: Nick Olmsted, Code 42 Software 24 | # Last Modified: 03-08-2013 25 | # 26 | # Uses relativedelta python module that can be downloaded from: 27 | # http://labix.org/python-dateutil 28 | # 29 | # Deactivates users devices based on the number of months since they have last connected to a master server 30 | # Params: 31 | # 1 arg - number of months (i.e 3) 32 | # 2 arg - type of logging (values: verbose, nonverbose) 33 | # 3 arg - set to deactivate devices or only print the devices that will be deactivated, but not deactivate them. 34 | # - values: deactivate, print 35 | # Example usages: 36 | # python deactivateDevices.py 3 verbose print 37 | # python deactivateDevices.py 3 noverbose deactivate 38 | # 39 | # NOTE: Make sure to set cpc_host, cpc_port, cpc_username, cpc_password to your environments values. 40 | # 41 | 42 | import sys 43 | 44 | import json 45 | 46 | import httplib 47 | 48 | import base64 49 | 50 | import math 51 | 52 | import calendar 53 | 54 | import logging 55 | 56 | from dateutil.relativedelta import * 57 | 58 | from datetime import * 59 | 60 | # Number of Months of no backup (should be a number) 61 | MAX_NUM_OF_MONTHS = str(sys.argv[1]) 62 | 63 | # verbose logging (should be text with words "verbose" or "noverbose") 64 | VERBOSE_LOGGING = str(sys.argv[2]) 65 | 66 | # Deactivate devices (should be text that equals "deactivate") 67 | RUN_DEACTIVATION_SCRIPT = str(sys.argv[3]) 68 | 69 | MAX_PAGE_NUM = 250 70 | NOW = datetime.now() 71 | 72 | # Set to your environments vlaues 73 | # Note: do not include http or http in the cpc_host environment variable 74 | cpc_host = "" 75 | cpc_port = "" 76 | 77 | cpc_username = " comparedate: 191 | if (VERBOSE_LOGGING == "verbose"): 192 | try: 193 | logging.debug("DEACTIVATE - device id: " + str(computerId) + " device name: " + str(deviceName) + " with last connected date of: " + str(lastConnected)) 194 | print "DEACTIVATE - device id: " + str(computerId) + " device name: " + str(deviceName) + " with last connected date of: " + str(lastConnected) 195 | except: 196 | #ignore name errors 197 | pass 198 | deactivateCount = deactivateCount + 1 199 | deactivateList.append(d) 200 | else: 201 | if (VERBOSE_LOGGING == "verbose"): 202 | logging.debug("IGNORE - device id: " + str(computerId) + " with last connected date of: " + str(lastConnected)) 203 | print "IGNORE - device id: " + str(computerId) + " with last connected date of: " + str(lastConnected) 204 | 205 | if (VERBOSE_LOGGING == "verbose"): 206 | logging.debug("END - getDevices - Building devices list request count: " + str(currentRequestCount)) 207 | print "END - getDevices - Building devices list request count: " + str(currentRequestCount) 208 | else: 209 | logging.debug("Building devices list... request count: " + str(currentRequestCount)) 210 | print "Building devices list... request count: " + str(currentRequestCount) 211 | 212 | if (VERBOSE_LOGGING == "verbose"): 213 | logging.debug("TOTAL Devices that are scheduled to be deactivated: " + str(deactivateCount)) 214 | logging.debug("END - getDevices") 215 | print "TOTAL Devices that are scheduled to be deactivated: " + str(deactivateCount) 216 | print "END - getDevices" 217 | 218 | return deactivateList 219 | 220 | # 221 | # Prints out all devices that will be deactivated 222 | # 223 | def printDevices(devices): 224 | count = 0 225 | if (VERBOSE_LOGGING == "verbose"): 226 | logging.debug("BEGIN - printDevices") 227 | print "BEGIN - printDevices" 228 | 229 | logging.debug("The following devices will be deactivated as they have not connected in more than " + str(MAX_NUM_OF_MONTHS) + " months:") 230 | print "The following devices will be deactivated as they have not connected in more than " + str(MAX_NUM_OF_MONTHS) + " months:" 231 | for d in devices: 232 | count = count + 1 233 | try: 234 | logging.debug("device name: " + str(d['name'])) 235 | print "device name: " + str(d['name']) 236 | except: 237 | #ignore any name exceptions 238 | pass 239 | 240 | if (VERBOSE_LOGGING == "verbose"): 241 | logging.debug("END - printDevices") 242 | print "END - printDevices" 243 | 244 | return count 245 | 246 | # 247 | # Calls the API to deactivate a single device 248 | # 249 | def deactivateDevice(computerId): 250 | 251 | headers = {"Authorization":getAuthHeader(cpc_username,cpc_password),"Accept":"application/json","Content-Type":"application/json"} 252 | 253 | try: 254 | 255 | conn = httplib.HTTPSConnection(cpc_host,cpc_port) 256 | 257 | conn.request("PUT","/api/ComputerDeactivation/" + str(computerId),None,headers) 258 | 259 | data = conn.getresponse().read() 260 | 261 | conn.close() 262 | 263 | # Since no response is returned from a PUT request as long as no exception is thrown we can assume the device was deactivated 264 | return "success" 265 | 266 | except httplib.HTTPException as inst: 267 | 268 | print "Exception in HTTP operations: %s" % inst 269 | 270 | return None 271 | 272 | except ValueError as inst: 273 | 274 | print "Exception decoding JSON: %s" % inst 275 | 276 | return None 277 | 278 | # 279 | # Returns logging levels based on passed in argument 280 | # 281 | def getLoggingLevel(): 282 | logging.basicConfig(filename='deactivateDevices.log',level=logging.DEBUG, format='%(asctime)s %(message)s') 283 | 284 | # 285 | # Deactivates devices if argument is set to deacivate 286 | # 287 | def deactivateDevices(): 288 | logging.debug("START - DeactivateDevices") 289 | 290 | if (VERBOSE_LOGGING == "verbose"): 291 | logging.debug("BEGIN - deactivateDevices") 292 | print "BEGIN - deactivateDevices" 293 | 294 | pageCount = getDevicesPageCount() 295 | devices = getDevices(pageCount) 296 | deviceCount = printDevices(devices) 297 | 298 | count = 0 299 | # Deactivate devices 300 | if (RUN_DEACTIVATION_SCRIPT == "deactivate"): 301 | logging.debug("RUN_DEACTIVATION_SCRIPT set to true") 302 | print "RUN_DEACTIVATION_SCRIPT set to true" 303 | 304 | for d in devices: 305 | succ = deactivateDevice(d["computerId"]) 306 | try: 307 | if succ: 308 | count = count + 1 309 | logging.debug("Deactivation successful for id: " + str(d["computerId"]) + " device name: " + str(d["name"])) 310 | print "Deactivation successful for id: " + str(d["computerId"]) + " device name: " + str(d["name"]) 311 | else: 312 | logging.debug("Deactivation unsuccessful for id: " + str(d["computerId"]) + " device name: " + str(d["name"])) 313 | print "Deactivation unsuccessful for id: " + str(d["computerId"]) + " device name: " + str(d["name"]) 314 | except: 315 | #ignore any name errors 316 | pass 317 | else: 318 | logging.debug("RUN_DEACTIVATION_SCRIPT set to false") 319 | print "RUN_DEACTIVATION_SCRIPT set to false" 320 | 321 | logging.debug("TOTAL devices schdeuled to be deactivated: " + str(deviceCount)) 322 | logging.debug("TOTAL devices deactivated: " + str(count)) 323 | print "TOTAL devices schdeuled to be deactivated: " + str(deviceCount) 324 | print "TOTAL devices deactivated: " + str(count) 325 | 326 | if (VERBOSE_LOGGING == "verbose"): 327 | logging.debug("END - deactivateDevices") 328 | print "END - deactivateDevices" 329 | 330 | logging.debug("END - DeactivateDevices") 331 | 332 | getLoggingLevel() 333 | deactivateDevices() 334 | -------------------------------------------------------------------------------- /AuthorityMigrationHelpers/cloudRoleAdder.py: -------------------------------------------------------------------------------- 1 | # By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no 2 | # cost separately from Code42's commercial offerings. This software is not provided under Code42's master services 3 | # agreement. It is provided AS-IS, without support, and subject to the license below. Any support and documentation 4 | # for this software are available at the Code42 community site. 5 | # 6 | # MIT LICENSE 7 | # Copyright (c) 2019 Code42 Software, Inc. 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 10 | # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 11 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 12 | # persons to whom the Software is furnished to do so, subject to the following conditions: 13 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 14 | # Software. 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 16 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | # 20 | # File: cloudRoleAdder.py 21 | # Author: A Orrison, Code42 Software 22 | # Last Modified: 2019-05-01 23 | # Built for Python 3 24 | 25 | import requests 26 | import argparse 27 | import getpass 28 | import json 29 | import pandas as pd 30 | import time 31 | import logging 32 | import sys 33 | import os# By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no 34 | # cost separately from Code42's commercial offerings. This software is not provided under Code42's master services 35 | # agreement. It is provided AS-IS, without support, and subject to the license below. Any support and documentation 36 | # for this software are available at the Code42 community site. 37 | # 38 | # MIT LICENSE 39 | # Copyright (c) 2019 Code42 Software, Inc. 40 | # 41 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 42 | # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 43 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 44 | # persons to whom the Software is furnished to do so, subject to the following conditions: 45 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 46 | # Software. 47 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 48 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 49 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 50 | # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 51 | # 52 | # File: cloudRoleAdder.py 53 | # Author: A Orrison, Code42 Software 54 | # Last Modified: 2019-05-01 55 | # Built for Python 3 56 | 57 | import requests 58 | import argparse 59 | import getpass 60 | import json 61 | import pandas as pd 62 | import time 63 | import logging 64 | import sys 65 | import os 66 | import urllib3 67 | 68 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 69 | parser = argparse.ArgumentParser(description='Input for this script') 70 | 71 | parser.add_argument('-s',dest='serverUrl',help='Server and port for the server ex: "https://server.url.code42.com:4285"',required=True) 72 | parser.add_argument('-u',dest='username',required=True,help='Username for a SYSADMIN user using local authentication') 73 | parser.add_argument('-e',action='store_true',help='Add this flag to run it for real. Leave out for a dry run') 74 | 75 | args = parser.parse_args() 76 | credsUsername = args.username 77 | serverAddress = args.serverUrl 78 | #print ( args.inputFile ) 79 | execute = args.e 80 | startTime = time.strftime("%y%m%d%I%M%S",time.localtime(time.time())) 81 | cwd = os.getcwd() 82 | 83 | cloudRoles = ["Admin Restore Limited","Admin Restore","Customer Cloud Admin","Desktop User","Org Admin","Org Help Desk","Org Legal Admin","Org Manager","Org Security Viewer","PROe User","Push Restore","Remote File Selection","Security Center User","Alert Emails","Desktop User - No Web Restore","Org Admin - No Web Restore","Cross Org Admin","Cross Org Admin - No Restore","Cross Org Help Desk","Cross Org Legal Admin","Cross Org Manager","Cross Org Security Viewer"] 84 | 85 | class Logger(object): 86 | def __init__(self): 87 | self.terminal = sys.stdout 88 | 89 | def write(self, message): 90 | self.terminal.write(message) 91 | with open("cloudRoleAdder-"+startTime+".log", "a") as f: 92 | f.write(message) 93 | 94 | def flush(self): 95 | pass 96 | sys.stdout = Logger() 97 | 98 | if not execute: 99 | print ( "This is a dry run. Add the -e flag to run for real.") 100 | else: 101 | print ("This wil add cloud roles to users \nIf you have second thoughts, or are not ready please quit now. (ctrl+c)") 102 | 103 | print ( "Username:\t",credsUsername, "\nServer Address:\t", serverAddress ) 104 | userPassword = getpass.getpass(prompt='Please enter your password:') 105 | 106 | def genericRequest(requestType,call, params={}, payload={}): 107 | address= serverAddress+call 108 | 109 | if requestType == 'get': 110 | r = requests.get(address,auth =(credsUsername,userPassword),params=params, verify=False) 111 | elif requestType == 'post' and execute: 112 | r = requests.post(address,auth =(credsUsername,userPassword),data=payload,params=params, verify=False) 113 | elif requestType == 'put' and execute: 114 | r = requests.put(address, auth =(credsUsername, userPassword),data=payload, params = params, verify=False) 115 | elif requestType == 'delete' and execute: 116 | r = requests.delete(address, auth =(credsUsername, userPassword),data=payload, params = params, verify=False) 117 | elif not execute: 118 | r = False 119 | else: 120 | print ( 'ERROR: Invalid Request type. Try again' ) 121 | r = False 122 | return r 123 | 124 | def testServerConnectivity(): 125 | try: 126 | response = genericRequest('get','/api/ping',params={}, payload={}) 127 | 128 | content =response.text 129 | data = json.loads(content) 130 | 131 | if response.status_code ==200 and data['data']['success'] == True: 132 | print ( "Server is accessible." ) 133 | except: 134 | print ( serverAddress, "is not available. Please check the server address and try again" ) 135 | exit() 136 | 137 | def testCredentials(): 138 | response = genericRequest('get', '/api/user/my?incRoles=True',params={}, payload={}) 139 | 140 | content = response.text 141 | data = json.loads(content) 142 | if response.status_code == 200: 143 | print ( "Credentials are good." ) 144 | print ( "\tthis user has the following roles:", data['data']['roles'] ) 145 | else: 146 | print ( data ) 147 | print ( "Exiting, please try your credentials again." ) 148 | exit() 149 | 150 | def getAllUsersByRole(roleId): 151 | payload = {} 152 | params = {} 153 | params['pgSize']=250 154 | params['pgNum']=1 155 | params['active']=True 156 | params['roleId']=roleId 157 | allUsers = [] 158 | response = genericRequest('get','/api/user', params=params,payload={}) 159 | firstContent = response.text 160 | firstData = json.loads(firstContent)['data'] 161 | totalUsers = firstData['totalCount'] 162 | pagesNeeded = totalUsers // params['pgSize'] + (totalUsers % params['pgSize'] > 0) 163 | while params['pgNum'] <= pagesNeeded: 164 | response = genericRequest('get', '/api/user', params=params, payload={}) 165 | content = response.text 166 | data = json.loads(content)['data'] 167 | 168 | allUsers.extend(data['users']) 169 | params['pgNum'] += 1 170 | 171 | return allUsers 172 | 173 | 174 | 175 | def createNonExistingCloudRole(newRoleName): 176 | params = {} 177 | toSend = {} 178 | toSend['roleName'] = newRoleName 179 | toSend['permissions'] = [] 180 | payload = json.dumps(toSend) 181 | createdRole = genericRequest('post', '/api/role', params=params, payload=payload) 182 | return createdRole 183 | def getRoles(): 184 | roles = {} 185 | params = {} 186 | payload = {} 187 | request = genericRequest('get','/api/role',params=params,payload=payload) 188 | allRoles = json.loads(request.content.decode('UTF-8'))['data'] 189 | 190 | for eachRole in allRoles: 191 | thisRole = {} 192 | numberOfUsers = eachRole['numberOfUsers'] 193 | 194 | roleName = eachRole['roleName'] 195 | thisRole['roleId'] = eachRole['roleId'] 196 | thisRole['Number of users'] = numberOfUsers 197 | thisRole['Name'] = roleName 198 | 199 | permissions = "" 200 | allPermissions = eachRole['permissions'] 201 | for eachPermission in allPermissions: 202 | permissions += eachPermission['permission'] + "," 203 | permissions = permissions[:-1] 204 | thisRole['Permissions'] = permissions 205 | default = eachRole['locked'] 206 | if default: 207 | thisRole['Type'] = "Default" 208 | else: 209 | thisRole['Type'] = "Custom" 210 | rolesReady = False 211 | if numberOfUsers > 0: 212 | roles[roleName] = thisRole 213 | dfRoles = pd.DataFrame(roles, columns=['RoleId', 'Name', 'Number of users', 'Type', 'Permissions']) 214 | dfRoles.to_csv(cwd+"/"+"AllRoles-"+startTime+".csv",encoding='utf-8',index=False) 215 | return roles 216 | 217 | def addRole(oldRoleName,newRoleName,userId): 218 | newparams = {} 219 | toSend = {} 220 | oldparams = {} 221 | oldparams['userId'] = userId 222 | oldparams['roleName']=oldRoleName 223 | call = '/api/UserRole' 224 | toSend['userId'] = userId 225 | toSend['roleName'] = newRoleName 226 | newPayload = json.dumps(toSend) 227 | #add the new role to the user 228 | roleAddRequest = genericRequest('post',call,params=newparams,payload=newPayload) 229 | return roleAddRequest 230 | 231 | 232 | testServerConnectivity() 233 | testCredentials() 234 | allRoles = getRoles() 235 | print ( "\nPlease refer to https://support.code42.com/Administrator/Cloud/Monitoring_and_managing/Manage_user_roles to determine what cloud roles you want to map your on premis roles to.\nYou will be using the role name\n\n" ) 236 | allResults = [] 237 | 238 | for eachRole in allRoles: 239 | if allRoles[eachRole]['Name'] not in cloudRoles: 240 | oldRoleName = eachRole 241 | oldRoleId = allRoles[eachRole]['roleId'] 242 | numUsers = allRoles[eachRole]['Number of users'] 243 | waitingForNewRoleName = True 244 | while waitingForNewRoleName: 245 | print ( "Please enter the new role name for the",numUsers,"user(s) who currently have", oldRoleName,". Type \"skip\" to skip this role and move onto the next") 246 | newRoleName = input("") 247 | if newRoleName != 'skip': 248 | print ( "You have chosen\n\tRole name:", newRoleName ) 249 | for eachCloudRole in cloudRoles: 250 | if newRoleName == eachCloudRole: 251 | print(newRoleName," is a cloud role") 252 | waitingForNewRoleName= False 253 | if waitingForNewRoleName == True: 254 | print ( "please choose a role present in the cloud." ) 255 | else: 256 | break 257 | if newRoleName != 'skip': 258 | print ( "Adding the role", newRoleName,"for", numUsers, "users with",oldRoleName) 259 | 260 | thisRoleUsers = getAllUsersByRole(oldRoleId) 261 | if execute: 262 | print("Trying to create", newRoleName, "if it does not exist already.") 263 | wasRoleMade = createNonExistingCloudRole(newRoleName) 264 | print("Result:",wasRoleMade) 265 | for user in thisRoleUsers: 266 | result = {} 267 | userId = user['userId'] 268 | username = user['username'] 269 | result['User Id'] = userId 270 | result['Username'] = username 271 | try: 272 | if execute: 273 | roleAddRequest = addRole(oldRoleName,newRoleName,userId) 274 | if roleAddRequest.status_code ==200: 275 | print ( "Added", newRoleName,"successfully for:",username ) 276 | result['Status'] = 'Success' 277 | else: 278 | print ( "Did not add", newRoleName,"to:",username ) 279 | result['Status'] = 'Failure' 280 | 281 | result['Old Role'] = oldRoleName 282 | result['New Role'] = newRoleName 283 | 284 | except: 285 | print ( "ERROR failed to add the new role",newRoleName,"for:", username ) 286 | result['Old Role'] = oldRoleName 287 | result['New Role'] = newRoleName 288 | result['Status'] = 'Failure' 289 | allResults.append(result) 290 | 291 | dfAllResults = pd.DataFrame(allResults,columns=['User Id','Username','New Role','Old Role','Status']) 292 | failures = dfAllResults.shape[0]-dfAllResults[dfAllResults.Status == 'Success'].shape[0] 293 | if execute: 294 | print ( "Completed. \nUsers changed:",dfAllResults[dfAllResults.Status == 'Success'].shape[0],"Users with an error:",failures ) 295 | else: 296 | print ( "This was a dry run. Use the -e flag to run for real. All lines in the resulting RolesAddedResults.csv will be marked 'Failure' " ) 297 | 298 | dfAllResults.to_csv(cwd+"/"+"RolesAddedResults-"+startTime+".csv", encoding='utf-8', index=False) 299 | 300 | print ( "Complete" ) 301 | import urllib3 302 | 303 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 304 | parser = argparse.ArgumentParser(description='Input for this script') 305 | 306 | parser.add_argument('-s',dest='serverUrl',help='Server and port for the server ex: "https://server.url.code42.com:4285"',required=True) 307 | parser.add_argument('-u',dest='username',required=True,help='Username for a SYSADMIN user using local authentication') 308 | parser.add_argument('-e',action='store_true',help='Add this flag to run it for real. Leave out for a dry run') 309 | 310 | args = parser.parse_args() 311 | username = args.username 312 | serverAddress = args.serverUrl 313 | #print ( args.inputFile ) 314 | execute = args.e 315 | startTime = time.strftime("%y%m%d%I%M%S",time.localtime(time.time())) 316 | cwd = os.getcwd() 317 | 318 | cloudRoles = ["Admin Restore Limited","Admin Restore","Customer Cloud Admin","Desktop User","Org Admin","Org Help Desk","Org Legal Admin","Org Manager","Org Security Viewer","PROe User","Push Restore","Remote File Selection","Security Center User","Alert Emails","Desktop User - No Web Restore","Org Admin - No Web Restore","Cross Org Admin","Cross Org Admin - No Restore","Cross Org Help Desk","Cross Org Legal Admin","Cross Org Manager","Cross Org Security Viewer"] 319 | 320 | class Logger(object): 321 | def __init__(self): 322 | self.terminal = sys.stdout 323 | 324 | def write(self, message): 325 | self.terminal.write(message) 326 | with open("cloudRoleAdder-"+startTime+".log", "a") as f: 327 | f.write(message) 328 | 329 | def flush(self): 330 | pass 331 | sys.stdout = Logger() 332 | 333 | if not execute: 334 | print ( "This is a dry run. Add the -e flag to run for real.") 335 | else: 336 | print ("This wil add cloud roles to users \nIf you have second thoughts, or are not ready please quit now. (ctrl+c)") 337 | 338 | print ( "Username:\t",username, "\nServer Address:\t", serverAddress ) 339 | userPassword = getpass.getpass(prompt='Please enter your password:') 340 | 341 | def genericRequest(requestType,call, params={}, payload={}): 342 | address= serverAddress+call 343 | 344 | if requestType == 'get': 345 | r = requests.get(address,auth =(username,userPassword),params=params, verify=False) 346 | elif requestType == 'post' and execute: 347 | r = requests.post(address,auth =(username,userPassword),data=payload,params=params, verify=False) 348 | elif requestType == 'put' and execute: 349 | r = requests.put(address, auth =(username, userPassword),data=payload, params = params, verify=False) 350 | elif requestType == 'delete' and execute: 351 | r = requests.delete(address, auth =(username, userPassword),data=payload, params = params, verify=False) 352 | elif not execute: 353 | r = False 354 | else: 355 | print ( 'ERROR: Invalid Request type. Try again' ) 356 | r = False 357 | return r 358 | 359 | def testServerConnectivity(): 360 | try: 361 | response = genericRequest('get','/api/ping',params={}, payload={}) 362 | 363 | content =response.text 364 | data = json.loads(content) 365 | 366 | if response.status_code ==200 and data['data']['success'] == True: 367 | print ( "Server is accessible." ) 368 | except: 369 | print ( serverAddress, "is not available. Please check the server address and try again" ) 370 | exit() 371 | 372 | def testCredentials(): 373 | response = genericRequest('get', '/api/user/my?incRoles=True',params={}, payload={}) 374 | 375 | content = response.text 376 | data = json.loads(content) 377 | if response.status_code == 200: 378 | print ( "Credentials are good." ) 379 | print ( "\tthis user has the following roles:", data['data']['roles'] ) 380 | else: 381 | print ( data ) 382 | print ( "Exiting, please try your credentials again." ) 383 | exit() 384 | 385 | def getAllUsersByRole(roleId): 386 | payload = {} 387 | params = {} 388 | params['pgSize']=250 389 | params['pgNum']=1 390 | params['active']=True 391 | params['roleId']=roleId 392 | allUsers = [] 393 | response = genericRequest('get','/api/user', params=params,payload={}) 394 | firstContent = response.text 395 | firstData = json.loads(firstContent)['data'] 396 | totalUsers = firstData['totalCount'] 397 | pagesNeeded = totalUsers // params['pgSize'] + (totalUsers % params['pgSize'] > 0) 398 | while params['pgNum'] <= pagesNeeded: 399 | response = genericRequest('get', '/api/user', params=params, payload={}) 400 | content = response.text 401 | data = json.loads(content)['data'] 402 | 403 | allUsers.extend(data['users']) 404 | params['pgNum'] += 1 405 | 406 | return allUsers 407 | 408 | def createNonExistingCloudRole(newRoleName): 409 | params = {} 410 | toSend = {} 411 | toSend['roleName'] = newRoleName 412 | toSend['permissions'] = [] 413 | payload = json.dumps(toSend) 414 | try: 415 | genericRequest('post', '/api/role', params=params, payload=payload) 416 | except: 417 | print ("Did not create role") 418 | 419 | def getRoles(): 420 | roles = {} 421 | params = {} 422 | payload = {} 423 | request = genericRequest('get','/api/role',params=params,payload=payload) 424 | allRoles = json.loads(request.content.decode('UTF-8'))['data'] 425 | 426 | for eachRole in allRoles: 427 | thisRole = {} 428 | numberOfUsers = eachRole['numberOfUsers'] 429 | 430 | roleName = eachRole['roleName'] 431 | thisRole['roleId'] = eachRole['roleId'] 432 | thisRole['Number of users'] = numberOfUsers 433 | thisRole['Name'] = roleName 434 | 435 | permissions = "" 436 | allPermissions = eachRole['permissions'] 437 | for eachPermission in allPermissions: 438 | permissions += eachPermission['permission'] + "," 439 | permissions = permissions[:-1] 440 | thisRole['Permissions'] = permissions 441 | default = eachRole['locked'] 442 | if default: 443 | thisRole['Type'] = "Default" 444 | else: 445 | thisRole['Type'] = "Custom" 446 | rolesReady = False 447 | 448 | roles[roleName] = thisRole 449 | dfRoles = pd.DataFrame(roles, columns=['RoleId', 'Name', 'Number of users', 'Type', 'Permissions']) 450 | dfRoles.to_csv(cwd+"/"+"AllRoles-"+startTime+".csv",encoding='utf-8',index=False) 451 | return roles 452 | 453 | def addRole(oldRoleName,newRoleName,userId): 454 | newparams = {} 455 | toSend = {} 456 | oldparams = {} 457 | oldPayload = {} 458 | oldparams['userId'] = userId 459 | oldparams['roleName']=oldRoleName 460 | call = '/api/UserRole' 461 | toSend['userId'] = userId 462 | toSend['roleName'] = newRoleName 463 | newPayload = json.dumps(toSend) 464 | 465 | if newRoleName != 'None': 466 | #add the new role to the user 467 | newRequest = genericRequest('post',call,params=newparams,payload=newPayload) 468 | 469 | testServerConnectivity() 470 | testCredentials() 471 | allRoles = getRoles() 472 | print ( "Please refer to https://support.code42.com/Administrator/Cloud/Monitoring_and_managing/Manage_user_roles to determine what cloud roles you want to map your on premises roles to.\nYou will be using the role name" ) 473 | allResults = [] 474 | 475 | for eachRole in allRoles: 476 | if allRoles[eachRole]['Name'] not in cloudRoles: 477 | oldRoleName = eachRole 478 | oldRoleId = allRoles[eachRole]['roleId'] 479 | numUsers = allRoles[eachRole]['Number of users'] 480 | if numUsers > 0: 481 | waitingForNewRoleName = True 482 | while waitingForNewRoleName: 483 | print ( "Please enter the new role name for users who currently have", oldRoleName,". Type \"skip\" to skip this role and move onto the next") 484 | newRoleName = input("") 485 | if newRoleName != 'skip': 486 | print ( "You have chosen\n\tRole name:", newRoleName ) 487 | for eachCloudRole in cloudRoles: 488 | if newRoleName == eachCloudRole: 489 | waitingForNewRoleName= False 490 | else: 491 | print ( "please choose a role present in the cloud." ) 492 | else: 493 | break 494 | if newRoleName != 'skip': 495 | print ( "Adding the role", newRoleName,"for", numUsers, "with",oldRoleName) 496 | 497 | thisRoleUsers = getAllUsersByRole(oldRoleId) 498 | for user in thisRoleUsers: 499 | result = {} 500 | userId = user['userId'] 501 | username = user['username'] 502 | result['User Id'] = userId 503 | result['Username'] = username 504 | try: 505 | createNonExistingCloudRole(newRoleName) 506 | if execute: 507 | print("Creating",newRoleName,"if it does not exist already.") 508 | addRole(oldRoleName,newRoleName,userId) 509 | print ( "Added", newRoleName,"successfully for:",username ) 510 | result['Old Role'] = oldRoleName 511 | result['New Role'] = newRoleName 512 | result['Status'] = 'Success' 513 | except: 514 | print ( "ERROR failed to add the new role",newRoleName,"for:", username ) 515 | result['Old Role'] = oldRoleName 516 | result['New Role'] = newRoleName 517 | result['Status'] = 'Failure' 518 | allResults.append(result) 519 | else: 520 | print ( "skipping this role" ) 521 | dfAllResults = pd.DataFrame(allResults,columns=['User Id','Username','New Role','Old Role','Status']) 522 | failures = dfAllResults.shape[0]-dfAllResults[dfAllResults.Status == 'Success'].shape[0] 523 | if execute: 524 | print ( "Completed. \nUsers changed:",dfAllResults[dfAllResults.Status == 'Success'].shape[0],"Users with an error:",failures ) 525 | else: 526 | print ( "This was a dry run. Use the -e flag to run for real. All lines in the resulting RolesAddedResults.csv will be marked 'Failure' " ) 527 | 528 | dfAllResults.to_csv(cwd+"/"+"RolesAddedResults-"+startTime+".csv", encoding='utf-8', index=False) 529 | 530 | print ( "Complete" ) 531 | -------------------------------------------------------------------------------- /restoreWatch.py: -------------------------------------------------------------------------------- 1 | # By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no cost separately from Code42's commercial offerings. 2 | # This software is not provided under Code42's master services agreement. 3 | # It is provided AS-IS, without support, and subject to the license below. 4 | # Any support and documentation for this software are available at the Code42 community site. 5 | 6 | # The MIT License (MIT) 7 | # Copyright (c) 2019 Code42 8 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 10 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 11 | # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 12 | # is furnished to do so, subject to the following conditions: 13 | 14 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 17 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | ''' 22 | SUMMARY: restoreWatch.py is a data leak prevention (DSP) solution that 23 | monitors users in the Code42 environment for restore activity that might 24 | be of concern. It can take actions to stop suspcious restores from 25 | occurring after detection, and warn admins and users about the activity. 26 | 27 | Version 1.3 28 | by Todd Ojala 29 | Date: 5/28/2015 30 | Original creation date: 4/24/2015 31 | 32 | Copyright (c) 2016 Code42, Inc. 33 | Permission is hereby granted, free of charge, to any person obtaining a copy 34 | of this software and associated documentation files (the "Software"), to deal 35 | in the Software without restriction, including without limitation the rights 36 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 37 | copies of the Software, and to permit persons to whom the Software is 38 | furnished to do so, subject to the following conditions: 39 | The above copyright notice and this permission notice shall be included in all 40 | copies or substantial portions of the Software. 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 42 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 43 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 44 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 45 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 46 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 47 | SOFTWARE. 48 | 49 | ** DISCLAIMER ** 50 | This script is provided as-is, and is not guaranteed to be suitable for 51 | any particular application. Use at your own risk. 52 | 53 | PURPOSE 54 | To prevent data leaks, detect restore activity of concern, and take 55 | action when such activity is detected. Actions include warning the 56 | admin, blocking admin, and blocking the user who has initiated the 57 | restores. 58 | 59 | OUTPUT 60 | The script produces the following output: 61 | 1. Email to administrator or designated recipient of warning messages. 62 | 2. CSV file with warnings, affected users, actions taken, and other 63 | information. This file is only created or appended to when a trigger 64 | event is detected! 65 | 3. restoreWatchData: a binary file that stores data needed by the script. 66 | This file is not user-viewable. 67 | 68 | REQUIRED PERMISSIONS 69 | The user credentials used to invoke this script must have SYSADMIN 70 | permission. 71 | 72 | WARNING 73 | This script will contain the credentials of a user with SYSADMIN rights 74 | on your Code42 master server in order to function as intended. 75 | ** Take appropriate precautions to restrict access to this file ** 76 | 77 | INSTRUCTIONS: 78 | 1. Copy the script to a safe location on your computer's filesystem. Set 79 | file permissions so that non-trusted users cannot read the contents 80 | of the file. 81 | 2. Set the variables under the "ADMIN PARAMETERS" section to values that 82 | work in your envirnment. WARNING: as this section may contain 83 | sensitive info, make sure that only trusted users have access to 84 | this file. 85 | 3. Set the variables under the "MONITORED USER PARAMETERS" section, or 86 | add multiple users to the "initial_data" dictionary object with 87 | care. 88 | 4. Set the email address for the person who should receive alerts. 89 | 5. Configure your system's cron job scheduler (or other scheduler) to 90 | run the script at an interval that is appropriate for your 91 | environment, e.g. every minute. 92 | 6. To reset the start of monitoring, simply delete or rename the file 93 | "restoreWatchData." 94 | 8. To change the monitoring parameters or actions after monitoring has 95 | started, change the constants under "MONITORED USER PARAMETERS", 96 | then delete the file "restoreWatchData." 97 | 98 | AVAILABLE TRIGGERS FOR WARNINGS, ALERTS AND ACTIONS 99 | 1. TOO_MANY_RESTORES_ACTION: What action to take if the number of 100 | restores of a user's data exceeds a defined rate. Values: NONE, 101 | WARN, BLOCK. 102 | 2. TOO_MANY_RESTORES_THRESHOLD: The threshold for the action in #1. 103 | Values: integers from 1 to a large number. This threshold is a count 104 | of the number of restores that have been occurred since the script 105 | last ran. The count starts over after each run. 106 | 3. NON_ORIGIN_DEVICE_RESTORE_ACTION: What action to take if a user's 107 | files are restored to a device that was not the source of the files. 108 | Values: NONE, WARN, BLOCK 109 | 4. NON_OWNER_RESTORE_ACTION: What action to take if a user's files are 110 | restored by a user who is not the owner of the files. Values: NONE, 111 | WARN, BLOCK 112 | 5. WEB_RESTORE_ACTION: What action to take if a user's files are 113 | restored via a web restore. Values: NONE, WARN, BLOCK 114 | 115 | TECHNICAL NOTES 116 | Working data about users in the monitored list, the restores that have 117 | been detected, actions to take, and thresholds are stored in the file 118 | "restoreWatchData." This file is not human readable, but may contain 119 | sensitive info. The file name may be changed under MISC PARAMETERS 120 | below, to facilitate running multiple instances of the script. 121 | 122 | The parameters are set only during the first time the script is run, and 123 | then stored in "restoreWatchData." The setup parameters will not be read 124 | again, unless the file "restoreWatchData" is deleted or renamed. To 125 | force the parameters to be read again, delete or rename the file 126 | "restoreWatchData". This also resets the monitoring "start time", and 127 | resets any other variables used by the script. 128 | 129 | The results of the restore audit are stored in a file named 130 | restoreWatch.csv. This file is cumulative, and stores all previous data 131 | written to it, even if monitoring is reset by deleting or renaming 132 | restoreWatchData. 133 | 134 | The CSV file name may be changed under MISC PARAMETERS below, to 135 | facilitate running multiple instances of the script. 136 | 137 | 138 | Modifications: 139 | 5/6/2015 Improved docstrings for each function, so that the Python help function will work for each function. 140 | Also improved email function by breaking out into separate function that can use mailx or smtplib based on user preference 141 | 5/8/2015 Further improvements to email functions. Mailx now uses the email address specified in the configuration variable/constant 142 | c42_admin_email. Smtplib can send successfully to an email server that uses SSL. 143 | 5/14/2015 Lots of changes, including support for sending email to the owner of the archive, and but fixes. 144 | 5/20/2015 Starting changes based on meeting with Peter L., Justin G., and Marc J. of 5/15/2015 145 | 5/27/2015 Improvements to comments, doc string, and general readability. 146 | 5/28/2015 Testing, added missing Web Restore event detection code. 147 | 148 | ''' 149 | 150 | # All modules below are part of the standard Python distribution 151 | import sys 152 | import os 153 | import json 154 | import csv 155 | import datetime 156 | import requests 157 | from requests.auth import HTTPBasicAuth 158 | import getpass 159 | import pickle # Used to serialize data before writing to disk 160 | import smtplib # Used to send email to those who get alerts (note being used in version 1.0) 161 | from email.mime.text import MIMEText 162 | import subprocess # Used to run Unix commands from within Python (currently sending email via mailx and bash) 163 | 164 | 165 | ## ADMIN PARAMETERS 166 | 167 | c42_master='https://master-server.example.com' 168 | c42_port='4285' 169 | c42_admin='admin' 170 | c42_password='serect_password' # Come up with a safer method for deployment. Store in file with restricted read access? 171 | c42_admin_email='c42admin@example.com' 172 | 173 | ## MONITORED USER PARAMETERS 174 | ## 175 | ## Enter a single user to monitor and the user's configuration, by setting the 176 | ## constants below. To enter multiple users, fill in the data 177 | ## dictionary "initial_data" with extra iterations of user entries by 178 | ## copying the first user's entry, then changing USERID to the actual 179 | ## userId's of the additional users. The other constants can be copied verbatim. 180 | 181 | USERID='1100' 182 | 183 | # Options are: NONE, WARN, BLOCK. Case sensitive. 184 | TOO_MANY_RESTORES_ACTION='WARN' 185 | TOO_MANY_RESTORES_THRESHOLD=3 186 | NON_ORIGIN_DEVICE_RESTORE_ACTION='WARN' 187 | NON_OWNER_RESTORE_ACTION='NONE' 188 | WEB_RESTORE_ACTION='BLOCK' 189 | 190 | ## The constants set below are not stored in the user state binary file, and can be changed after monitoring has started, without resetting/deleting binary file. 191 | 192 | ## REPORTING SETTINGS 193 | EMAIL_ARCHIVE_OWNER=True 194 | 195 | ## MISC PARAMETERS 196 | DATA_FILE='restoreWatchData' 197 | CSV_FILE='restoreWatch.csv' 198 | 199 | ## SSL SECURITY SETTINGS 200 | # Verify SSL certs for requests to Code42 API. Set to False if your 201 | # master server's cert is self-certified to avoid fatal error. Case 202 | # sensitive. 203 | VERIFY_CERT=False 204 | 205 | 206 | ## EMAIL SETTINGS 207 | USE_MAILX=False 208 | MAIL_HOST='smtp.your_email_server.com' 209 | SMTP_USE_SSL=True 210 | SMTP_PORT='Default' # Set to string "Default" to use defaults set by SMTP Lib, or to an integer value. 211 | SMTP_REQUIRES_AUTH=True 212 | SMTP_USER='user@your_email_server.com' 213 | SMTP_PASS='secret_password' 214 | SMTP_SENDING_USER=SMTP_USER 215 | 216 | 217 | ## Modify initial_data dictionary object with care. 218 | initial_data={ USERID:{ 219 | 'too_many_restores_action':TOO_MANY_RESTORES_ACTION, 220 | 'too_many_restores_threshold':TOO_MANY_RESTORES_THRESHOLD, 221 | 'non_origin_device_restore_action':NON_ORIGIN_DEVICE_RESTORE_ACTION, 222 | 'non_owner_restore_action':NON_OWNER_RESTORE_ACTION, 223 | 'web_restore_action':WEB_RESTORE_ACTION } 224 | } 225 | 226 | # *** Do not change anything below this line! *** 227 | 228 | now=str(datetime.datetime.now()) 229 | default_key_values={'monitorStartTime':now, 230 | 'restores':[{'restoreId':'1'}], 231 | 'too_many_restores_action':'NONE', 232 | 'too_many_restores_threshold':3, 233 | 'non_origin_device_restore_action':'WARN', 234 | 'non_owner_restore_action':'WARN', 235 | 'web_restore_action':'WARN', 'firstLook':True} 236 | 237 | 238 | def getStoredDataFromDisk(file): 239 | ''' Retrieves the serialized user data and program data from disk 240 | where it is stored between script executions. ''' 241 | try: 242 | data_from_file=pickle.load(open(file, 'rb')) 243 | except IOError: 244 | data_from_file={} 245 | return data_from_file 246 | 247 | 248 | def storeDataToDisk(file, data): 249 | ''' Serializes and stores user data and program data to disk. ''' 250 | pickle.dump(data, open(file,'wb')) # The 'w' option overwrites any existing file 251 | 252 | 253 | def verifyData(data): 254 | ''' Verifies that each required key in the main data dictionary used to 255 | store user data, actions, thresholds, etc. exists. If not, create 256 | the key and give it a default value. ''' 257 | for user in data: 258 | for key in default_key_values: 259 | if data[user].has_key(key) == False: 260 | data[user][key]=default_key_values[key] 261 | 262 | 263 | def getNewUserRestores(user): 264 | ''' Retrieve up to the last 100 restores for this user or for all users 265 | if no user supplied. ''' 266 | # This function should be improved to page back to the last stored 267 | # restore record if necessary. Currently, if more than 100 restores 268 | # were performed for this user between runs, restores over 100 will 269 | # be ignored. 270 | request_url=c42_master+':'+c42_port+'/api/restoreRecord' 271 | if user is not None: 272 | payload={'sourceUserId': user } 273 | else: 274 | payload={} 275 | new_restores_json=requests.get(request_url,auth=(c42_admin, c42_password), params=payload, verify=VERIFY_CERT) 276 | new_restores_converted_to_python_object=json.loads(new_restores_json.text)['data'] 277 | return new_restores_converted_to_python_object['restoreRecords'] 278 | 279 | def getRecentRestores(from_hours_ago=24): 280 | ''' 281 | Retrieve all restores going back in time by the defined number of hours. The default number of hours is 24. 282 | ''' 283 | ## Remove this from master branch 284 | 285 | def newRestoreEvents(user,user_data,restore_events): 286 | ''' Returns only the restores that are new for this user since the last 287 | monitoring event (the last time the script was run). ''' 288 | # If this is the first time this user has been monitored, this is a 289 | # special case: just store the last restore as the benchmark for 290 | # change. # If there was no "last restore," then nothing will be 291 | # inserted, but the first seeded restore with restoreID of 1 will be 292 | # there, and the # flag firstLook will be changed to false 293 | if user_data['firstLook']==True: 294 | user_data['firstLook']=False 295 | try: 296 | last_restore=restore_events[0] 297 | except IndexError: 298 | last_restore=False 299 | if last_restore is not False: 300 | user_data['restores'].append(last_restore) 301 | return [] 302 | 303 | else: # 304 | temp_list_restores=[] 305 | for restore in restore_events: 306 | restoreId=restore['restoreId'] 307 | if int(restoreId)>int(user_data['restores'][-1]['restoreId']): # The restoreId of the restore event is greater than the restoreId of the last one in the list, so append it. I assume that the restoreIds increase with each new restore. 308 | temp_list_restores.append(restore) 309 | temp_list_restores.reverse() 310 | return temp_list_restores 311 | 312 | 313 | def buildReport(report_data, restore, event_type, action, result, monitorStartTime): 314 | ''' 315 | Build the CSV report by adding a new entry to the list containing the data. 316 | ''' 317 | row={} 318 | row['Event type']=event_type 319 | row['restoreId']=restore['restoreId'] 320 | row['startDate']=restore['startDate'] 321 | row['Device owner Id']=restore['sourceUserId'] 322 | row['Restore user Id']=restore['requestingUserId'] 323 | row['File count']=restore['fileCount'] 324 | row['Type']=restore['type'] 325 | row['Action']=action 326 | row['Action result']=result 327 | row['Monitoring start']=monitorStartTime 328 | 329 | report_data.append(row) 330 | 331 | def blockUser(user): 332 | ''' Block specified user using the UserBlock resource of the Code42 333 | API. ''' 334 | request_url=c42_master+':'+c42_port+'/api/UserBlock/'+str(user) 335 | r=requests.put(request_url,auth=(c42_admin, c42_password),verify=VERIFY_CERT) 336 | 337 | if r.status_code==201: 338 | return 'SUCCESS' 339 | else: 340 | return 'FAIL' 341 | 342 | def sendEmail(use_mailx, email_address, email_string): 343 | ''' Write admin email to disk, but only if there were events to 344 | report In the current version, using mailx requires the email text 345 | to be on a disk file. ''' 346 | f=open('restoreWatchEmail.tmp','w') 347 | f.write(email_string) 348 | f.close() 349 | 350 | if use_mailx==True: 351 | mail_cmd="cat restoreWatchEmail.tmp | mailx -s 'RestoreWatch alert!' "+c42_admin_email 352 | p=subprocess.Popen(mail_cmd, shell=True, stdout=subprocess.PIPE) 353 | ## Errors for sending email can be caught below. Use for testing script. 354 | ## output, errors = p.communicate() 355 | ## print errors,output 356 | os.remove('restoreWatchEmail.tmp') 357 | 358 | else: 359 | email_file=open('restoreWatchEmail.tmp', 'rb') 360 | email_msg=MIMEText(email_file.read()) 361 | email_file.close() 362 | 363 | email_msg['Subject']= 'restoreWatch Data Leak & Protection Report' 364 | email_msg['From'] = SMTP_USER 365 | email_msg['To'] = email_address 366 | 367 | if SMTP_USE_SSL==False: 368 | if SMTP_PORT is not 'Default': 369 | s=smtplib.SMTP(MAIL_HOST, SMTP_PORT) 370 | else: 371 | s=smtplib.SMTP(MAIL_HOST) 372 | else: 373 | if SMTP_PORT is not 'Default': 374 | s=smtplib.SMTP_SSL(MAIL_HOST, SMTP_PORT) 375 | else: 376 | s=smtplib.SMTP_SSL(MAIL_HOST) 377 | if SMTP_REQUIRES_AUTH==True: 378 | s.login(SMTP_USER,SMTP_PASS) 379 | s.sendmail( SMTP_SENDING_USER, email_address, email_msg.as_string() ) 380 | s.quit() 381 | os.remove('restoreWatchEmail.tmp') 382 | 383 | def getUserEmail(userId): 384 | ''' Retrieve the email address of the user using the API ''' 385 | request_url=c42_master+':'+c42_port+'/api/User/'+str(userId) 386 | user_info_json=requests.get(request_url,auth=(c42_admin, c42_password), verify=VERIFY_CERT) 387 | convert_user_info_to_python_object=json.loads(user_info_json.text)['data'] 388 | email=convert_user_info_to_python_object['email'] 389 | if '@' not in email: 390 | email=None 391 | return email 392 | 393 | 394 | def main(): 395 | 396 | report_data=[] # This list contains the report to send via email and output to a CSV file on disk 397 | admin_email_string='SUMMARY OF ALERTS AND ACTIONS\n' 398 | separator="***********************************************\n" 399 | 400 | temp_data=getStoredDataFromDisk(DATA_FILE) 401 | if len(temp_data)==0: # If there is no stored data, then load initial data from variables set in script 402 | data= initial_data.copy() 403 | verifyData(data) 404 | else: 405 | data=temp_data.copy() 406 | 407 | 408 | for user in data: 409 | user_data=data[user] 410 | monitorStartTime=user_data['monitorStartTime'] 411 | user_email_string='ALERT FROM RESTORE WATCH REGARDING YOUR DATA\n' 412 | user_email_trigger=False 413 | 414 | if user=='ALL': 415 | user=None # When user none is passed to getNewUserRestores, returned restores are not restricted to a user 416 | restore_events=getNewUserRestores(user) 417 | 418 | # Further processing is required to determine which events are new, by 419 | # comparing the events to the previously stored events 420 | new_restores=newRestoreEvents(user,user_data,restore_events) 421 | 422 | # Append the new restores for this user to the current data file in memory 423 | user_data['restores'].extend(new_restores) 424 | 425 | # Check for non-owner restore 426 | if user_data['non_owner_restore_action']<>'NONE': 427 | action=user_data['non_owner_restore_action'] 428 | result='N/A' 429 | for restore in new_restores: 430 | sourceUserId=restore['sourceUserId'] 431 | if restore['requestingUserId'] <> sourceUserId: 432 | if action=='BLOCK': 433 | result=blockUser(int(restore['requestingUserId'])) 434 | buildReport(report_data, restore, 'Non-owner restore', action, result, monitorStartTime) 435 | user_email_trigger=True 436 | 437 | # Build the string to send in email about this event 438 | str1="Non-owner restore detected for user with GUID {}.\n".format(sourceUserId) 439 | str2="Action taken: {}\n".format(action) 440 | str3="The restoring user's GUID is {}\n".format(restore['requestingUserId']) 441 | str4="The accepting device is {}\n".format(restore['acceptingComputerGuid']) 442 | str5="Action result: {}\n".format(result) 443 | user_email_string+=str1+str2+str3+str4+str5 444 | user_email_string+="\nPlease contact your CrashPlan administrator.\n{}".format(separator) 445 | admin_email_string+=str1+str2+str3+str4+str5 446 | admin_email_string+="\nPlease view the CSV file {} for more details.\n{}".format(CSV_FILE, separator) 447 | 448 | 449 | 450 | # Check for non-origin device restore 451 | if user_data['non_origin_device_restore_action']<>'NONE': 452 | action=user_data['non_origin_device_restore_action'] 453 | result='N/A' 454 | for restore in new_restores: 455 | sourceUserId=restore['sourceUserId'] 456 | if restore['sourceComputerGuid'] <> restore['acceptingComputerGuid']: 457 | if action=='BLOCK': 458 | result=blockUser(int(restore['requestingUserId'])) 459 | buildReport(report_data, restore, 'Non-origin device restore', action, result, monitorStartTime) 460 | user_email_trigger=True 461 | 462 | # Build the string to send in email about this event 463 | str1="Non-origin device restore detected for user with GUID {}.\n".format(sourceUserId) 464 | str2="Action taken: {}\n".format(action) 465 | str3="The restoring user's GUID is {}\n".format(restore['requestingUserId']) 466 | str4="The accepting device is {}\n".format(restore['acceptingComputerGuid']) 467 | str5="Action result: {}\n".format(result) 468 | user_email_string+=str1+str2+str3+str4+str5 469 | user_email_string+="\nPlease contact your CrashPlan administrator.\n{}".format(separator) 470 | admin_email_string+=str1+str2+str3+str4+str5 471 | admin_email_string+="\nPlease view the CSV file {} for more details.\n{}".format(CSV_FILE, separator) 472 | 473 | # Check for Web restore 474 | if user_data['web_restore_action']<>'NONE': 475 | action=user_data['web_restore_action'] 476 | result='N/A' 477 | for restore in new_restores: 478 | sourceUserId=restore['sourceUserId'] 479 | if restore['type']=='WEB': 480 | if action=='BLOCK': 481 | result=blockUser(int(restore['requestingUserId'])) 482 | buildReport(report_data, restore, 'Web restore detected', action, result, monitorStartTime) 483 | user_email_trigger=True 484 | 485 | # Build the string to send in email about this event 486 | str1="Web restore detected for user with GUID {}.\n".format(sourceUserId) 487 | str2="Action taken: {}\n".format(action) 488 | str3="The restoring user's GUID is {}\n".format(restore['requestingUserId']) 489 | str4="The accepting device is {}\n".format(restore['acceptingComputerGuid']) 490 | str5="Action result: {}\n".format(result) 491 | user_email_string+=str1+str2+str3+str4+str5 492 | user_email_string+="\nPlease contact your CrashPlan administrator.\n{}".format(separator) 493 | admin_email_string+=str1+str2+str3+str4+str5 494 | admin_email_string+="\nPlease view the CSV file {} for more details.\n{}".format(CSV_FILE, separator) 495 | 496 | 497 | # Check for too many restores. 498 | if user_data['too_many_restores_action']<>'NONE': 499 | action=user_data['too_many_restores_action'] 500 | result='N/A' 501 | restore_count=len(new_restores) 502 | 503 | if restore_count>user_data['too_many_restores_threshold']: 504 | restore=new_restores[-1] # The restore to use for determining the suspect user to block taken from last in series (most recent) 505 | sourceUserId=restore['sourceUserId'] # Only makes sense to set this for when user is not ALL (None) 506 | if action=='BLOCK' and user is not None: # A block action doesn't make sense for all users and possibly different users doing restores 507 | result=blockUser(int(restore['requestingUserId'])) 508 | buildReport(report_data, restore, 'Too many restores', action, result, monitorStartTime) #Include the very last restore in the report 509 | user_email_trigger=True 510 | 511 | # Build the string to send in email about this event 512 | str1="Too many restores detected for user with GUID {}.\n".format(sourceUserId) # This doesn't really make sense for ALL users 513 | str2="Action taken: {}\n".format(action) 514 | str3="The restoring user's GUID is {}\n".format(restore['requestingUserId']) # This doesn't really make sense for ALL users 515 | str4="The accepting device is {}\n".format(restore['acceptingComputerGuid']) # This doesn't really make sense for ALL users 516 | str5="Total number of restores since last monitoring event: {}\n".format(str(restore_count)) 517 | str6="Action result: {}\n".format(result) 518 | user_email_string+=str1+str2+str3+str4+str5+str6 519 | user_email_string+="\nPlease contact your CrashPlan administrator.\n{}".format(separator) 520 | admin_email_string+=str1+str2+str3+str4+str5+str6 521 | admin_email_string+="\nPlease view the CSV file {} for more details.\n{}".format(CSV_FILE, separator) 522 | 523 | if user_email_trigger==True and EMAIL_ARCHIVE_OWNER==True: 524 | user_email=getUserEmail(user) 525 | if user_email is not None: 526 | sendEmail(USE_MAILX, user_email, user_email_string) 527 | 528 | ## Create CSV report and write to disk file. 529 | if len(report_data)>0: 530 | keys= report_data[0].keys() 531 | 532 | # Output the report data to a disk file in CSV format. 533 | with open(CSV_FILE, 'ab+') as csv_file: # Append the new data to the current file. Delete the csv file manually if desired. 534 | dict_writer = csv.DictWriter(csv_file, keys) 535 | dict_writer.writeheader() 536 | dict_writer.writerows(report_data) 537 | 538 | 539 | sendEmail(USE_MAILX, c42_admin_email, admin_email_string) 540 | 541 | # Store the important user and config data to disk between runs 542 | storeDataToDisk(DATA_FILE, data) 543 | 544 | 545 | main() 546 | -------------------------------------------------------------------------------- /AccessLockUtility/access_lock.ps1: -------------------------------------------------------------------------------- 1 | # By downloading and executing this software, you acknowledge and agree that Code42 is providing you this software at no cost separately from Code42's commercial offerings. 2 | # This software is not provided under Code42's master services agreement. 3 | # It is provided AS-IS, without support, and subject to the license below. 4 | # Any support and documentation for this software are available at the Code42 community site. 5 | 6 | # The MIT License (MIT) 7 | # Copyright (c) 2019 Code42 8 | 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 10 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 11 | # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 12 | # is furnished to do so, subject to the following conditions: 13 | 14 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 17 | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | # 21 | # 22 | # Code42 Access Lock Utility - v1.2 23 | # 24 | # Description: This script will remotely lock or unlock access on devices through 25 | # the Access Lock feature of Code42. Clients are required to be on Windows using 26 | # Bitlocker full-disk encryption. (6.0+) 27 | # 28 | # https://support.code42.com/Administrator/6/Monitoring_and_managing/Access_Lock 29 | # 30 | # Author: Kyle Hatlestad 31 | # 32 | # 33 | ######################################################## 34 | # Catch parameters passed to script 35 | ######################################################## 36 | Param( 37 | [Parameter(Mandatory=$False)] 38 | [string]$FullServerUrl, 39 | [string]$user, 40 | [string]$password, 41 | [string]$deviceGuid, 42 | [string]$report, 43 | 44 | [Parameter(Mandatory=$False)] 45 | [ValidateSet('LOCK','UNLOCK')] 46 | [string]$action 47 | ) 48 | 49 | $scriptVersion = "1.2" 50 | ######################################################## 51 | # Functions 52 | ######################################################## 53 | #---------------------------------------------------------------------------------------# 54 | # Perform Lock/Unlock per paramters passed # 55 | #---------------------------------------------------------------------------------------# 56 | function errorHandler { 57 | param( [Parameter(Mandatory=$false)][string]$errorMessage) 58 | Write-Host "Error: " $_; Write-Host "`n"; Write-Host "Terminating script."; Write-Host "`n" 59 | & cmd /c pause 60 | Exit 61 | } 62 | #---------------------------------------------------------------------------------------# 63 | # Perform Lock/Unlock per paramters passed # 64 | #---------------------------------------------------------------------------------------# 65 | function getUserName { 66 | param( [Parameter(Mandatory=$true)][string]$FullServerUrl, [Parameter(Mandatory=$true)][HashTable]$headers, [Parameter(Mandatory=$true)][string]$userUid) 67 | $resourceurl = '/api/User/' + $userUid + '?idType=uid' 68 | $uri = $FullServerUrl + $resourceurl 69 | try { 70 | $getUserInfo = invoke-RestMethod -Uri $uri -Method GET -Headers $headers 71 | } catch { 72 | Write-Host "`n"; Write-Host "Error: " $_; Write-Host "`n" 73 | } 74 | return $getuserInfo.data.username 75 | } 76 | #---------------------------------------------------------------------------------------# 77 | # Report on Lock/Unlock Status # 78 | #---------------------------------------------------------------------------------------# 79 | function runReport { 80 | param( [Parameter(Mandatory=$true)][string]$FullServerUrl, [Parameter(Mandatory=$true)][string]$CPUserName, [Parameter(Mandatory=$true)][string]$password, [Parameter(Mandatory=$true)][string]$report) 81 | $EncodedAuthorization = [System.Text.Encoding]::UTF8.GetBytes($CPUserName + ':' + $password) 82 | $EncodedPassword = [System.Convert]::ToBase64String($EncodedAuthorization) 83 | $headers = @{"Authorization" = "Basic $($EncodedPassword)"} 84 | $baseurl = $FullServerUrl 85 | $resourceurl = '/c42api/v3/auth/jwt' 86 | $uri = $baseurl + $resourceurl 87 | # Create session cookie 88 | try { 89 | $cookieRequest = invoke-WebRequest -Uri $uri -Method GET -Headers $headers -SessionVariable myWebSession 90 | } catch { 91 | errorHandler $_ 92 | } 93 | # If -report is set to 'all', then generate a report for all devices 94 | if ($report -eq "all") { 95 | Write-Host "`n" 96 | Write-Host "Creating an Access Lock report on all devices can be a time consuming process. Note only devices that have ever been locked or unlocked will be reported. Continue?" 97 | $Readhost = Read-Host " ( y / n ) " 98 | switch ($ReadHost) { 99 | Y {$runReport=$true} 100 | N {$runReport=$false} 101 | Default {$runReport=$false} 102 | } 103 | if(-Not($runReport)) { 104 | Write-Host "Terminating script." 105 | Exit 106 | } 107 | # Create an array for the CSV values and establish variables for the Computer API call 108 | $numColsToExport = 11 109 | $holdarr = @() 110 | $pNames = @("LockingUserUid","LockingUserName","UnlockingUserUid","UnlockingUserName","IsLocked","LockEnabledDate","UnlockedDate","LockPasshprase","LastClientResponseDate","ModificationDate","CreationDate") 111 | $deviceCount = 1 112 | $currentPage = 1 113 | $pageSize = 250 114 | $keepLooping = $true 115 | $deviceLockStatus = "False" 116 | While ($keepLooping) { 117 | $resourceurl = '/api/Computer?pgSize=' + $pageSize + '&pgNum=' + $currentPage 118 | $uri = $baseurl + $resourceurl 119 | try { 120 | $deviceInfo = invoke-RestMethod -Uri $uri -Method GET -Headers $headers 121 | foreach ($comp in $deviceInfo.data.computers) { 122 | [string]$FullVersion = $comp.version 123 | [int]$cpversionLength = $FullVersion.length 124 | [string]$cpversion = $FullVersion.Substring($cpversionLength-3) 125 | # Only check the access lock status for devices on 6.0.0 or greater 126 | If ($cpversion -ge 600) { 127 | $resourceurl = '/c42api/v3/AccessLock/' + $comp.guid 128 | $uri = $baseurl + $resourceurl 129 | try { 130 | $lockingUserName, $unlockingUserName = "" 131 | $lockStatusRequest = invoke-RestMethod -Uri $uri -Method GET -ContentType $ContentType -WebSession $myWebSession 132 | $lockData = $lockStatusRequest.data 133 | # The resultset only includes User Uid, so figure out the user name 134 | if ($lockData.lockingUserUid) { 135 | $lockingUserName = getUserName $baseurl $headers $lockData.lockingUserUid 136 | } 137 | if ($lockData.unlockingUserUid) { 138 | $unlockingUserName = getUserName $baseurl $headers $lockData.unlockingUserUid 139 | } 140 | # Establish an array of all the Access Lock info 141 | $row = @($lockData.lockingUserUid,$lockingUserName,$lockData.unlockingUserUid,$unlockingUserName,$lockData.isLockEnabled,$lockData.lockEnabledDate,$lockData.unlockDate,$lockData.lockPassphrase,$lockData.lastClientResponseDate,$lockData.modificationDate,$lockData.creationDate) 142 | # Add the data for the device to an object and add it to the larger arrow to export to CSV 143 | $obj = new-object PSObject 144 | for ($i=0;$i -lt $numColsToExport;$i++) { 145 | $obj | add-member -membertype NoteProperty -name $pNames[$i] -value $row[$i] 146 | } 147 | $holdarr += $obj 148 | $obj = $null 149 | } catch { 150 | $catcherror = ($error[0] | out-string) 151 | # If the device has never been locked or unlocked before, the status check will throw an certain error in not finding the status 152 | if($catcherror -notcontains "accessLock_not_found_for_device") { 153 | $deviceLockStatus = "False" 154 | } else { 155 | errorHandler $_ 156 | } 157 | } 158 | } 159 | $deviceCount += 1 160 | } 161 | } catch { 162 | errorHandler $_ 163 | } 164 | if ($deviceInfo.data.computers.length -gt 0) { 165 | $keepLooping = $true 166 | Write-Host "Processed " $deviceCount " devices." 167 | } else { 168 | $keepLooping = $false 169 | } 170 | $currentPage += 1 171 | } 172 | Write-Host "Processed " $deviceCount " devices in total." 173 | $resultsLogFileName = $(Get-Date -format M.d.yy.hh.mm.ss) 174 | # Export the results to a CSV file 175 | $holdarr | export-csv AccessLockReport_$resultsLogFileName.csv -NoTypeInformation 176 | Write-Host "`n" 177 | Write-Host "See AccessLockReport_$resultsLogFileName.log for full details" 178 | Write-Host "`n" 179 | } else { 180 | # If not 'all', then it should be a specific device GUID 181 | $resourceurl = '/c42api/v3/AccessLock/' + $report 182 | $uri = $baseurl + $resourceurl 183 | try { 184 | $deviceLockInfo = invoke-RestMethod -Uri $uri -Method GET -ContentType $ContentType -WebSession $myWebSession 185 | } catch { 186 | errorHandler $_ 187 | } 188 | $lockReport = $deviceLockInfo.data 189 | # The resultset only includes User Uid, so figure out the user name 190 | if ($lockReport.lockingUserUid) 191 | { 192 | $lockingUserName = getUserName $baseurl $headers $lockReport.lockingUserUid 193 | } 194 | if ($lockReport.unlockingUserUid) 195 | { 196 | $unlockingUserName = getUserName $baseurl $headers $lockReport.unlockingUserUid 197 | } 198 | Write-Host "The userUid of the user who locked the device: " $lockReport.lockingUserUid 199 | Write-Host "The user name of the user who locked the device: " $lockingUserName 200 | Write-Host "The userUid of the user who unlocked the device: " $lockReport.unlockingUserUid 201 | Write-Host "The user name of the user who unlocked the device: " $unlockingUserName 202 | Write-Host "Lock status (True=LOCKED, False=UNLOCKED): " $lockReport.isLockEnabled 203 | Write-Host "Date and time device was unlocked: " $lockReport.unlockDate 204 | Write-Host "The passphrase used to lock the device. Use this passphrase to unlock the device: " $lockReport.lockPassphrase 205 | Write-Host "The date and time the Code42 cloud last received an update from the device: " $lockReport.lastClientResponseDate 206 | Write-Host "The date and time any Access Lock settings for this device were modified: " $lockReport.modificationDate 207 | Write-Host "The date and time Access Lock was initiated: " $lockReport.creationDate 208 | 209 | $resultsLogFileName = $(Get-Date -format M.d.yy.hh.mm.ss) 210 | "Locking User Uid: " + $lockReport.lockingUserUid | Out-File -append "AccessLockReport_$resultsLogFileName.log" 211 | "Locking User Name: " + $lockingUserName | Out-File -append "AccessLockReport_$resultsLogFileName.log" 212 | "Unlocking User Uid: " + $lockReport.unlockingUserUid | Out-File -append "AccessLockReport_$resultsLogFileName.log" 213 | "Unlocking User Name: " + $unlockingUserName | Out-File -append "AccessLockReport_$resultsLogFileName.log" 214 | "Lock Status: " + $lockReport.isLockEnabled | Out-File -append "AccessLockReport_$resultsLogFileName.log" 215 | "Date/Time of Unlock: " + $lockReport.unlockDate | Out-File -append "AccessLockReport_$resultsLogFileName.log" 216 | "Lock Passphrase: " + $lockReport.lockPassphrase | Out-File -append "AccessLockReport_$resultsLogFileName.log" 217 | "Date/Time of last checkin: " + $lockReport.lastClientResponseDate | Out-File -append "AccessLockReport_$resultsLogFileName.log" 218 | "Date/Time Access Lock settings were last modified: " + $lockReport.modificationDate | Out-File -append "AccessLockReport_$resultsLogFileName.log" 219 | "Date/Time Access Lock was initiated: " + $lockReport.creationDate | Out-File -append "AccessLockReport_$resultsLogFileName.log" 220 | Write-Host "`n" 221 | Write-Host "See AccessLockReport_$resultsLogFileName.log for full details" 222 | Write-Host "`n" 223 | } 224 | & cmd /c pause 225 | Exit 226 | } 227 | 228 | #---------------------------------------------------------------------------------------# 229 | # Perform Lock/Unlock per paramters passed # 230 | #---------------------------------------------------------------------------------------# 231 | function commandLineOperation { 232 | param( [Parameter(Mandatory=$true)][string]$FullServerUrl, [Parameter(Mandatory=$true)][string]$CPUserName, [Parameter(Mandatory=$true)][string]$password, [Parameter(Mandatory=$true)][string]$deviceguid, [Parameter(Mandatory=$true)][string]$lockAction) 233 | $EncodedAuthorization = [System.Text.Encoding]::UTF8.GetBytes($CPUserName + ':' + $password) 234 | $EncodedPassword = [System.Convert]::ToBase64String($EncodedAuthorization) 235 | $headers = @{"Authorization" = "Basic $($EncodedPassword)"} 236 | $baseurl = $FullServerUrl 237 | $resourceurl = '/c42api/v3/auth/jwt' 238 | $uri = $baseurl + $resourceurl 239 | # Create session cookie 240 | try { 241 | $cookieRequest = invoke-WebRequest -Uri $uri -Method GET -Headers $headers -SessionVariable myWebSession 242 | } catch { 243 | errorHandler $_ 244 | } 245 | $resourceurl = '/c42api/v3/AccessLock/' + $deviceGuid 246 | $uri = $baseurl + $resourceurl 247 | # Determine if it's a LOCK(POST) or UNLOCK(PATCH) action 248 | $requestMethod="PATCH" 249 | switch ($action) 250 | { 251 | UNLOCK {$requestMethod="PATCH"} 252 | LOCK {$requestMethod="POST"} 253 | default {$requestMethod="PATCH"} 254 | } 255 | # Send lock/unlock command 256 | Try 257 | { 258 | $lockRequest = invoke-RestMethod -Uri $uri -Method $requestMethod -ContentType $ContentType -WebSession $myWebSession 259 | } 260 | Catch 261 | { 262 | errorHandler $_ 263 | } 264 | # Gather computer/device info including device name 265 | $resourceurl = '/api/Computer/' + $deviceGuid + '?idType=guid' 266 | $uri = $baseurl + $resourceurl 267 | Try 268 | { 269 | $deviceInfo = invoke-RestMethod -Uri $uri -Method GET -Headers $headers 270 | } 271 | Catch 272 | { 273 | errorHandler $_ 274 | } 275 | $deviceName = $deviceInfo.data.name 276 | # Call function to output the results to command window and file. 277 | lockOutput $lockRequest $requestMethod $baseurl $CPUserName $deviceName $deviceGuid 278 | # Wait for a key to continue 279 | & cmd /c pause 280 | Exit 281 | } 282 | #---------------------------------------------------------------------------------------# 283 | # Output of Lock/Unlock Section # 284 | #---------------------------------------------------------------------------------------# 285 | function lockOutput { 286 | param( [Parameter(Mandatory=$true)][array]$lockRequest,[string]$requestMethod,[string]$baseurl,[string]$CPUserName,[string]$selectedDeviceName,[string]$selectedDeviceGuid) 287 | if ($requestMethod -eq "PATCH" ) 288 | { 289 | $resultsLogFileName = $(Get-Date -format M.d.yy.hh.mm.ss) 290 | $lockRequestResult = $lockRequest.data 291 | if ($lockRequestResult.lockingUserUid) 292 | { 293 | $lockingUserName = getUserName $baseurl $headers $lockRequestResult.lockingUserUid 294 | } 295 | "Locking User Uid: " + $lockRequestResult.lockingUserUid | Out-File -append "UnlockResults_$resultsLogFileName.log" 296 | "Locking User Name: " + $lockingUserName | Out-File -append "UnlockResults_$resultsLogFileName.log" 297 | "Unlocking User Uid: " + $lockRequestResult.unlockingUserUid | Out-File -append "UnlockResults_$resultsLogFileName.log" 298 | "Unlocking User Name: " + $CPUserName | Out-File -append "UnlockResults_$resultsLogFileName.log" 299 | "Unlock Date: " + $lockRequestResult.unlockDate | Out-File -append "UnlockResults_$resultsLogFileName.log" 300 | "Bitlocker Lock Passphrase: " + $lockRequestResult.lockPassphrase | Out-File -append "UnlockResults_$resultsLogFileName.log" 301 | 302 | Write-Host "The device " $selectedDeviceName " is unlocked. The passphrase to enter into Bitlocker on the device is:" 303 | Write-Host "`n" 304 | Write-Host -ForegroundColor Green $lockRequestResult.lockPassphrase 305 | Write-Host "`n" 306 | Write-Host -ForegroundColor Red "WARNING: Save this lockpassphrase! If you lock the device again (by submitting another POST request) before entering the lockPassphrase on the locked device, a new passphrase is created on the Code42 server, but the device is still locked with the original lockPassphrase." 307 | Write-Host "`n" 308 | Write-Host "See UnlockResults_$resultsLogFileName.log for full details" 309 | Write-Host "`n" 310 | } else { 311 | $resultsLogFileName = $(Get-Date -format M.d.yy.hh.mm.ss) 312 | "Locking User Name: " + $CPUserName | Out-File -append "LockResults_$resultsLogFileName.log" 313 | "Lock Date/Time: " + $(Get-Date -format G) | Out-File -append "LockResults_$resultsLogFileName.log" 314 | "Locked Device Name: " + $selectedDeviceName | Out-File -append "LockResults_$resultsLogFileName.log" 315 | "Locked Device GUID: " + $selectedDeviceGuid | Out-File -append "LockResults_$resultsLogFileName.log" 316 | "Lock Results: " + $lockRequest.data | Out-File -append "LockResults_$resultsLogFileName.log" 317 | 318 | [string]$lockResponseData = $lockRequest.data 319 | Write-Host $lockResponseData 320 | # Catch errors starting with HELPER_APP_. Those typically mean a problem invoking Bitlocker 321 | if ($lockResponseData.IndexOf("HELPER_APP") -ge 0) { 322 | Write-Host "`n" 323 | Write-Host "If the response results in a 'HELPER_APP_###' message, that typically indicates an issue in envoking Bitlocker. Ensure that Bitlocker is enabled and configured on the device. See documentation at https://support.code42.com/Administrator/Cloud/Monitoring_and_managing/Access_Lock for more information. " 324 | Write-Host "`n" 325 | } elseif ($lockResponseData.IndexOf("deviceLockableResult=OK") -ge 0 -and $lockResponseData.IndexOf("deviceRestarting=True") -ge 0) { 326 | Write-Host "`n" 327 | Write-Host "The device was successfully locked and has restared" 328 | Write-Host "`n" 329 | } 330 | Write-Host "`n" 331 | Write-Host "See LockResults_$resultsLogFileName.log for full details" 332 | Write-Host "`n" 333 | } 334 | } 335 | 336 | ################################### 337 | # Variables (Customize) 338 | ################################### 339 | [string]$WebProtocol = "https" 340 | [string]$global:ContentType = "application/json" 341 | 342 | ############################################################## 343 | # Check if running command-line or interactive 344 | ############################################################## 345 | if ($FullServerUrl) { 346 | if ($report) { 347 | runReport $FullServerUrl $user $password $report 348 | } else { 349 | commandLineOperation $FullServerUrl $user $password $deviceGuid $action 350 | } 351 | } 352 | 353 | ################################### 354 | # Intro Screen 355 | ################################### 356 | 357 | Write-Host "####################################################################################" 358 | Write-Host "`n" 359 | Write-Host " Code42 Access Lock Utility" 360 | Write-Host "`n" 361 | Write-Host " Script Version: " $scriptVersion 362 | Write-Host "`n" 363 | Write-Host " This utiity is designed to perform an access lock on computer devices with the Code42" 364 | Write-Host " CrashPlan 6.0+ client or above on Windows OS devices. It can also be used to unlock" 365 | Write-Host " devices previously locked and provide the Bitlocker recovery passphrase key." 366 | Write-Host "`n" 367 | Write-Host " Requirements:" 368 | Write-Host " * Code42 Server 6.0+ or Code42 Fully Hosted" 369 | Write-Host " * Code42 Enterprise License or Security Tools Add-On License" 370 | Write-Host " * Code42 CrashPlan 6.0+ client" 371 | Write-Host " * Windows OS configured with Bitlocker full-disk encryption" 372 | Write-Host "`n" 373 | 374 | Write-Host "####################################################################################" 375 | 376 | ################################### 377 | # Inputs 378 | ################################### 379 | # Your master server address (e.g. code42.company.com, 177.14.34.193, etc) 380 | $CPServer = Read-Host -Prompt 'Input your server address and web port(no http/s needed. e.g. company.c.code42.com:4285)' 381 | # Username and Password prompt 382 | $CPUserName = Read-Host -Prompt 'Input username' 383 | $CPSecureUserPassword = Read-Host 'Input password' -AsSecureString 384 | $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($CPSecureUserPassword) 385 | $CPUserPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) 386 | $EncodedAuthorization = [System.Text.Encoding]::UTF8.GetBytes($CPUserName + ':' + $CPUserPassword) 387 | $EncodedPassword = [System.Convert]::ToBase64String($EncodedAuthorization) 388 | $headers = @{"Authorization" = "Basic $($EncodedPassword)"} 389 | 390 | ################################### 391 | # Verify user has proper permissions 392 | ################################### 393 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 394 | $FullCPServerAddress = $WebProtocol + "://" + $CPServer 395 | $resourceurl = '/api/User?incAll=true&active=true&q=' + $CPUserName 396 | $uri = $FullCPServerAddress + $resourceurl 397 | 398 | Write-Host "Trying " $uri 399 | try { 400 | 401 | $adminUser = invoke-RestMethod -Uri $uri -Method GET -Headers $headers 402 | } catch { 403 | [string]$catcherror = ($error[0] | out-string) 404 | # If using a self-signed cert, Powershell will not allow a connection. Use HTTP instead. 405 | if($catcherror.IndexOf("SSL/TLS") -ge 0) { 406 | $WebProtocol = "http" 407 | $FullCPServerAddress = $WebProtocol + "://" + $CPServer 408 | $uri = $FullCPServerAddress + $resourceurl 409 | try { 410 | $adminUser = invoke-RestMethod -Uri $uri -Method GET -Headers $headers 411 | } catch { 412 | errorHandler $_ 413 | } 414 | } else { 415 | errorHandler $_ 416 | } 417 | } 418 | if ($adminUser.data.users.roles -contains "SYSADMIN" -Or $adminUser.data.users.roles -contains "Customer Cloud Admin") { 419 | #Permisions are good. They have one of the right roles 420 | } else { 421 | Write-Host "`n"; Write-Host "User " $CPUserName " does not have permissions to run the Access Lock commands. The role 'SYSADMIN' or 'Customer Cloud Admin' is required." 422 | errorHandler $_ 423 | } 424 | # Store the User Uid of the user performing the lock/unlock 425 | $CPUserUid = $adminUser.data.users.userUid 426 | 427 | # Check if the server is 6.0 or above. If fully-hosted using crashplan.com, then it will be fine 428 | if ($FullCPServerAddress -notcontains "crashplan.com") { 429 | $resourceurl = '/api/Server' 430 | $uri = $FullCPServerAddress + $resourceurl 431 | try { 432 | $serverInfo = invoke-RestMethod -Uri $uri -Method GET -Headers $headers 433 | [string]$fullServerVersion = $serverInfo.data.servers.version 434 | [int]$majorVersion = $fullServerVersion.substring(0,1) 435 | if ($majorVersion -le 5) { 436 | Write-Host "`n"; Write-Host "The version of your server is " $fullServerVersion " and does not support Access Lock. You must be on version 6.0 or greater." 437 | errorHandler $_ 438 | } 439 | } catch { 440 | errorHandler $_ 441 | } 442 | } 443 | 444 | ################################### 445 | # Find user and their device(s) 446 | ################################### 447 | $userCount = 0 448 | while ( $userCount -ne 1){ 449 | # User ID whose device you need to lock/unlock 450 | $lockUser = Read-Host -Prompt 'Enter the user ID for the owner of the device that is needed to lock or unlock access' 451 | $resourceurl = '/api/User?incAll=true&active=true&q=' + $lockUser 452 | $uri = $FullCPServerAddress + $resourceurl 453 | try { 454 | $userRequest = invoke-RestMethod -Uri $uri -Method GET -Headers $headers 455 | } catch { 456 | errorHandler $_ 457 | } 458 | $userCount = $userRequest.data.totalCount 459 | if ($userCount -eq 0) { 460 | # If the count is 0, no user is found 461 | } elseif ($userCount -gt 1) { 462 | #if the count is greather then 1, then more then one user was found 463 | foreach ($userFound in $userRequest.data.users) { 464 | if ($lockUser -eq $userFound.username) { 465 | # Pull the User Uid from the resultset 466 | $userUid = $userFound.userUid 467 | $userCount = 1 468 | } 469 | } 470 | if ($userCount -gt 1) { 471 | Write-Host "User not found. Please enter a valid user ID." 472 | Write-Host "`n" 473 | } 474 | } else { 475 | # Pull the User Uid from the resultset 476 | $userUid = $userRequest.data.users.userUid 477 | } 478 | } 479 | 480 | # Envoke the request to establish a session cookie with the new v3 API call that the Access 481 | # Lock API requires 482 | $resourceurl = '/c42api/v3/auth/jwt' 483 | $uri = $FullCPServerAddress + $resourceurl 484 | try { 485 | $cookieRequest = invoke-WebRequest -Uri $uri -Method GET -Headers $headers -SessionVariable myWebSession 486 | } catch { 487 | errorHandler $_ 488 | } 489 | 490 | # Get the list of devices under the user 491 | $resourceurl = '/api/Computer?userUid=' + $userUid + '&incBackupUsage=true&active=true' 492 | $uri = $FullCPServerAddress + $resourceurl 493 | try { 494 | $deviceList = invoke-RestMethod -Uri $uri -Method GET -Headers $headers 495 | } catch { 496 | errorHandler $_ 497 | } 498 | 499 | # Loop through the devices and show each one as a choice along with their information and current lock status 500 | $deviceALGuids = @() 501 | $deviceAlNames = @() 502 | $deviceLockStatusArray = @() 503 | $deviceLockStatus = "False" 504 | $i = 1 505 | 506 | $noALCompName = @() 507 | $noALCompGuid = @() 508 | $noALCompLastConnected = @() 509 | $noALCompVersion = @() 510 | $j = 1 511 | 512 | $k = 0 513 | $displayStatus="UNLOCKED" 514 | 515 | Write-Host "`n" 516 | Write-Host "Computers available for Access Lock" 517 | Write-Host "===================================================================" 518 | foreach ($comp in $deviceList.data.computers) { 519 | #If the client is on Windows and 6.0 or greater, make it a choice. If not, create an array of the ineligible devices 520 | [string]$FullVersion = $comp.version 521 | [int]$cpversionLength = $FullVersion.length 522 | [string]$cpversion = $FullVersion.Substring($cpversionLength-3) 523 | if ($cpversion -ge 600) { 524 | # If the device is 6.0+, we next need to check the current status of the lock. 525 | $resourceurl = '/c42api/v3/AccessLock/' + $comp.guid 526 | $uri = $FullCPServerAddress + $resourceurl 527 | try { 528 | $lockStatusRequest = invoke-RestMethod -Uri $uri -Method GET -ContentType $ContentType -WebSession $myWebSession 529 | $deviceLockStatus = $lockStatusRequest.data.isLockEnabled 530 | } catch { 531 | $catcherror = ($error[0] | out-string) 532 | # If the device has never been locked or unlocked before, the status check will throw an certain error in not finding the status 533 | if($catcherror.Contains("accessLock_not_found_for_device")) { 534 | $deviceLockStatus = "False" 535 | } else { 536 | errorHandler $_ 537 | } 538 | } 539 | switch ($deviceLockStatus) 540 | { 541 | true {$textColor="Red";$displayStatus="LOCKED"} 542 | false {$textColor="Green";$displayStatus="UNLOCKED"} 543 | default {$textColor="Green";$displayStatus="UNLOCKED"} 544 | } 545 | # Now display the information as a selection for locking/unlocking the device 546 | Write-Host "[$i] Computer Name: " $comp.name " | GUID: "$comp.guid " | LastConnected: "$comp.lastConnected " | Current Access Lock Status: " -NoNewLine; Write-Host -ForegroundColor $textColor $displayStatus 547 | $deviceALGuids += $comp.guid 548 | $deviceAlNames += $comp.name 549 | $deviceLockStatusArray += $deviceLockStatus 550 | $i++ 551 | $k++ 552 | } else { 553 | # build a list of devices that are not available for locking/unlocking 554 | $deviceNoALGuids += $comp.guid 555 | $noALCompName += $comp.name 556 | $noALCompGuid += $comp.guid 557 | $noALCompLastConnected += $comp.lastConnected 558 | $noALCompVersion += $cpversion 559 | $j++ 560 | $k++ 561 | } 562 | 563 | } 564 | Write-Host "[$i] None - Exit" 565 | Write-Host "`n" 566 | Write-Host "Computers not eligible for Access Lock" 567 | Write-Host "===================================================================" 568 | 569 | for ($l=0; $l -lt $k-1; $l++) { 570 | Write-Host "Computer Name: " $noALCompName[$l] " | GUID: "$noALCompGuid[$l] " | LastConnected: "$noALCompLastConnected[$l] " | Client Version: " $noALCompVersion[$l] 571 | } 572 | Write-Host "`n" 573 | 574 | [int]$srccomp = read-host -prompt "`nPlease choose the computer number from list above to lock/unlock" 575 | if ($srccomp -eq $i -Or $srccomp -eq "") { 576 | Write-Host "Terminating script."; Write-Host "`n" 577 | & cmd /c pause 578 | Exit 579 | } 580 | $selectedDeviceGuid = $deviceALGuids[$srccomp-1] 581 | $selectedDeviceName = $deviceALNames[$srccomp-1] 582 | $selectedDeviceLockStatus = $deviceLockStatusArray[$srccomp-1] 583 | # Based on the current status of the device, perform the opposite action to lock/unlock the device 584 | # the POST method will lock and the PATCH method with unlock 585 | $requestMethod="PATCH" 586 | switch ($selectedDeviceLockStatus) 587 | { 588 | true {$requestMethod="PATCH"} 589 | false {$requestMethod="POST"} 590 | default {$requestMethod="PATCH"} 591 | } 592 | [string]$ContentType = "application/json" 593 | $resourceurl = '/c42api/v3/AccessLock/' + $selectedDeviceGuid 594 | $uri = $FullCPServerAddress + $resourceurl 595 | try { 596 | $lockRequest = invoke-RestMethod -Uri $uri -Method $requestMethod -ContentType $ContentType -WebSession $myWebSession 597 | } catch { 598 | Write-Host "`n"; Write-Host "Error: " $_; Write-Host "`n" 599 | } 600 | Write-Host "`n" 601 | lockOutput $lockRequest $requestMethod $FullCPServerAddress $CPUserName $selectedDeviceName $selectedDeviceGuid 602 | # Wait for a key to continue 603 | & cmd /c pause 604 | Exit 605 | --------------------------------------------------------------------------------