├── .gitignore
├── requirements.txt
├── README.md
└── jss-advanced_search-to-google_sheets.py
/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 | include
3 | pip*
4 | lib
5 | .Python
6 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | cffi==1.2.1
2 | cryptography==1.0.2
3 | enum34==1.0.4
4 | google-api-python-client==1.4.2
5 | gspread==0.2.5
6 | httplib2==0.9.2
7 | idna==2.0
8 | ipaddress==1.0.14
9 | oauth2client==1.5.1
10 | pyasn1==0.1.9
11 | pyasn1-modules==0.0.8
12 | pycparser==2.14
13 | pyOpenSSL==0.15.1
14 | requests==2.8.0
15 | rsa==3.2
16 | simplejson==3.8.0
17 | six==1.10.0
18 | uritemplate==0.6
19 | wheel==0.24.0
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JSS-Advanced-Search-to-Google-Sheets
2 | Take any Computer/Mobile Advanced Search in the JSS and publish it to Google Sheets
3 | Great for sharing reports with people that don't need access to your JSS
4 | Run it on a schedule to deliver reports on time, every time!
5 |
6 | ## Features
7 |
- Take any Advanced Computer or Mobile Device Search and turn it into a Google Spreadsheet.
8 | - Any columns that are included in the Display tab will be automatically included in the sheet.
9 | - Share the Google Sheet with only those who need the information.
10 |
11 | ## Requirements:
12 | A little bit of time on your part - It's not too bad though, just a lot of things to setup the first time.
13 | Google Apps for Domain/Education
14 | Access to create an account, turn on developer console for an org, and Manage API Client Access
15 | A JAMF Software Server (Tested against version 9.8)
16 | An account with API read permissions to Mobile Device/Computer Advanced Searches and Mobiles Devices/Computers
17 |
18 | # How to get this all setup
19 | ## Create a new Google Apps User
20 | Go to admin.google.com and sign in with an admin account
21 | Create an OU called Developers or similar
22 | Create a new user for the project and put them in that organization
23 |
24 | ## Give permissions to the organization
25 | Go to Apps
26 | Go to Additional Services
27 | Go to Google Developers Console
28 | Turn on for some organizations and select the Developers organization
29 |
30 | ## Create the developer project so you can get the OAuth key
31 | Go to the Google Developers Console: https://console.developers.google.com/
32 | Sign in with your new Google Apps Developer account
33 | Go to Select a Project --> Create Project
34 | Give it a meaningful name
35 | Read and agree to the terms and click create
36 | Go to API and Auth, Click on APIs
37 | Enable Google Drive API
38 | Click on Credentials, Click Add Credentials
39 | Click on Service Account
40 | Select p12 as the key type
41 | Click create
42 | Click close
43 | You now have a service account, click on the email address
44 | Copy the Client ID and email address to a safe location
45 | The p12 key should download to your computer
46 |
47 | ## Authorize the API account
48 | Go back to admin.google.com
49 | Click on Security
50 | Check Enable API Access
51 | Scroll down to Advanced settings and click Manage API client Access
52 | Paste the Client ID from the credentials page into Client Name
53 | Paste https://spreadsheets.google.com/feeds/ into One or More API Scopes
54 | Click Authorize
55 | You should now see an entry for your Client ID and Spreadsheets (Read/Write)
56 |
57 | ## Setup a Google Sheet
58 | Sign into drive.google.com as the api user or yourself and create a new sheet.
59 | Share that sheet with the API user if you used another account
60 | Give the Workbook a name
61 | Give the Sheet a name
62 | Get the spreadsheet key (Part of the URL that is before /edit )
63 | Looks like this: 1pasdfsBr_8a3anlLDIdiSLENlsdnOK9s7bJqhdGow
64 |
65 | ## Create advanced search
66 | Create an advanced search as desired, find the id (found in the URL)
67 |
68 | ## Get the script
69 | Download this project
70 | Create a folder next to the script called data
71 | Create a folder inside data called key
72 | Put your .p12 key into the key folder and call it sheets.p12
73 | Open your favorite terminal
74 | cd into project folder
75 | Run: ```sudo pip install -r requirements.txt```
76 |
77 | ## Lets set the variables
78 | Set workbook key
79 | Set worksheet name
80 | Set google_api_user (This is the long email address we generated in the developer console)
81 | Set google_user (This is the developer email address - what you logged into Sheets and Developer console with)
82 | Set jss settings, host, username, password, etc
83 | Set the as_id (Advanced Search ID)
84 | Set the as_type by uncommenting
85 |
86 | ## Run it!
87 | ```python jss-advanced_search-to-google_sheets.py```
88 |
89 | ## TIPS
90 | Cron it! or launchd it!
91 | Format your spreadsheet - updates will not modify format. Hidden columns will remain hidden. Bold cells will remain bold.
92 | Conditional Formatting can be good for dealing with unknown table lengths
93 |
94 | ## Known Limitations
95 | Can have more than 26 columns - should be easy to accomodate, just a day two thing...
96 | I think you will have to have a Google Apps for Domain/Education account. This will not work with a personal account that I am aware of.
97 | Right now there are a few columns that come down whether or not they are selected. This could be handled by the script.
98 |
99 | ## Warranty
100 | I offer no warranty for this script and am not liable if I blow up your JSS or GAFE environment :)
101 |
102 | ## Next steps:
103 | Make it so it can loop over multiple sheets and reports so only one script is needed to handle multiple reports.
104 | Add more than 26 columns
105 | Ability to hide UDID, name, and id fields unless specified by the report.
106 | Logging
107 |
108 | ## Suggestion
109 | Are welcome!
110 |
--------------------------------------------------------------------------------
/jss-advanced_search-to-google_sheets.py:
--------------------------------------------------------------------------------
1 | # JSS Advanced Search to Google Sheet
2 | # Authored by Brad Schmidt on 10/8/2015
3 | # Requires a Google developer account, project, and key. See DOCS for more information on how to obtain
4 | # the necessary OAuth information for Google Sheets API to function
5 | # The OAuth key will be stored in ./data/key/sheets.p12
6 | # The credentials will be stored in ./data/sheets.dat
7 |
8 | # Please fill out the variables below
9 |
10 | # Variables
11 | workbook_key = ""
12 | worksheet_name = ""
13 |
14 | # Oauth
15 | google_api_user = ''
16 | google_user = ''
17 |
18 | # JSS Authentication
19 | jss_host = "" # Include http:// or https:// leave off port number Example: https://your.jss.com
20 | jss_port = "8443" # Port number
21 | jss_path = "" # Context -- if you need it: Enter it with a forward slash. Example: If your JSS is https://your.jss.com:8443/dev you would enter /dev
22 | jss_username = "" # Setup a user with API rights to read Advanced Computer and Mobile Reports as well as Computers and Mobile Devices
23 | jss_password = "" # Password
24 |
25 | # Advanced search ID and Type
26 | as_id = ""
27 |
28 | # Advanced search ID Type -- Uncomment one
29 | #as_type = "Computer"
30 | #as_type = "Mobile"
31 |
32 | ################################################################
33 | ######### You should not have to modify below this line ########
34 | ################################################################
35 |
36 | ################################################################
37 | ##################---Importing Libraries---#####################
38 | ################################################################
39 | import sys
40 | import httplib2
41 | import string
42 | import googleapiclient
43 | import oauth2client
44 | from apiclient.discovery import build
45 | from oauth2client.file import Storage
46 | from oauth2client.client import SignedJwtAssertionCredentials
47 | from oauth2client.client import OAuth2WebServerFlow
48 |
49 | # OS path to pickup files,etc
50 | import os.path
51 | SITE_ROOT = os.path.dirname(os.path.realpath(__file__))
52 | # Handles json parsing
53 | import json
54 |
55 | # Allows us to work with the Google Sheet
56 | import gspread
57 |
58 | # requests makes the JSS connection
59 | import requests
60 |
61 | # Mobile or Computer Search - set values
62 | def as_type_f(as_type):
63 | if as_type == "Computer":
64 | return "advancedcomputersearches","advanced_computer_search","computers"
65 | if as_type == "Mobile":
66 | return "advancedmobiledevicesearches","advanced_mobile_device_search","mobile_devices"
67 | else:
68 | print 'Failed to set as_type properly.\rPlease uncomment as_type = "Computer" or as_type = "Mobile"'
69 |
70 | # Get the report data
71 | def getReportData(as_path,as_key,as_rows):
72 | # Make the request of the JSS API
73 | r = requests.get(jss_host + ':' + jss_port + jss_path + '/JSSResource/' + as_path + '/id/' + as_id, headers={'Accept': 'application/json'}, auth=(jss_username,jss_password))
74 | # logging.info("Response: %s" %r.text)
75 |
76 | # Get the device data we need
77 | report_data = r.json()[as_key]
78 |
79 | # Get the header values
80 | for c in report_data[as_rows]:
81 | columns = c.keys()
82 |
83 | # Return the data and the headings
84 | return report_data[as_rows],columns
85 |
86 | # This function authenticates the Sheets session
87 | def oauthSheets():
88 | ####--logging.info("Authenticating using OAuth for Google Sheets")
89 | f = file('%s/%s' % (SITE_ROOT,'data/key/sheets.p12'), 'rb')
90 | key = f.read()
91 | f.close()
92 | http = httplib2.Http()
93 | storage = Storage(SITE_ROOT + '/data/sheets.dat')
94 | credentials = storage.get()
95 | if credentials is None or credentials.invalid:
96 | credentials = SignedJwtAssertionCredentials(google_api_user, key, scope='https://spreadsheets.google.com/feeds/',sub=google_user)
97 | storage.put(credentials)
98 | else:
99 | credentials.refresh(http)
100 | http = httplib2.Http()
101 | http = credentials.authorize(http)
102 | gc = gspread.authorize(credentials)
103 | return gc
104 |
105 | def publish(report,columns):
106 | ####--logging.info("Checking to see if the user has filled out the UA")
107 | # Oauth Sheet
108 |
109 | gc = oauthSheets()
110 | # Select the sheet
111 | wks = gc.open_by_key(workbook_key)
112 | worksheet = wks.worksheet(worksheet_name)
113 |
114 | # Clear out existing data from the sheet
115 | # Get length of spreadsheet
116 | rows = worksheet.col_values(1)
117 | number_of_rows = len(rows)
118 |
119 | # Get the width of the spreadsheet
120 | cols = worksheet.row_values(1)
121 | number_of_columns = len(cols)
122 |
123 | # If the sheet is blank skip over the clear portion. A range error will occur if it is blank
124 | if number_of_columns != 0 and number_of_rows != 0:
125 |
126 | # Now that we have the coordinates let's set the range
127 | cell_list = worksheet.range('A1:%s%s' % (string.uppercase[number_of_columns],number_of_rows))
128 |
129 | # Set each cell to ""
130 | for cell in cell_list:
131 | cell.value = ""
132 |
133 | # Update the cells all at once
134 | worksheet.update_cells(cell_list)
135 |
136 | # Setup the Header row
137 | # Get the number of columns provided by the Advanced Search
138 | number_of_columns = len(columns) - 1
139 |
140 | # Set the cell range
141 | cell_list = worksheet.range('A1:%s1' % string.uppercase[number_of_columns])
142 |
143 | # Create a list of column header values
144 | header_data = []
145 | for header in columns:
146 | header_data.append(header)
147 |
148 | # Iterates through each value in the list and each cell
149 | for heading, cell in zip(header_data,cell_list):
150 | cell.value = heading
151 |
152 | # Update the sheet with column headers
153 | worksheet.update_cells(cell_list)
154 |
155 | # Prepare the data from the Advanced Search
156 | search_data = []
157 | for line in report:
158 | for header in columns:
159 | cell = line.get(header)
160 | search_data.append(cell)
161 |
162 | # Let's see how many rows are in the report
163 | rows = len(report)
164 |
165 | # Select a range
166 | cell_list = worksheet.range('A2:%s%s' % (string.uppercase[number_of_columns],rows + 1))
167 |
168 | # Set the cell value while iterting through values and cells
169 | for value, cell in zip(search_data,cell_list):
170 | cell.value = value
171 |
172 | # Update the spreadsheet with the report data
173 | worksheet.update_cells(cell_list)
174 |
175 | # Is it Mobile or Computer, return approprtiate values for parsing the report
176 | # Let's make sure the value was uncommented
177 | try:
178 | as_type
179 | as_path,as_key,as_rows = as_type_f(as_type)
180 | except NameError:
181 | print 'Failed to set as_type properly.\rPlease uncomment as_type = "Computer" or as_type = "Mobile"'
182 | sys.exit(1)
183 |
184 | # Get the data from the advanced search
185 | report,columns = getReportData(as_path,as_key,as_rows)
186 |
187 | # Publish to Google Sheet
188 | publish(report,columns)
189 |
--------------------------------------------------------------------------------