From 9593b71df011b250f45b6814f46f567033d51668 Mon Sep 17 00:00:00 2001 From: Oleksii Shevchuk Date: Wed, 3 Oct 2018 16:33:34 +0300 Subject: [PATCH] Add isearch module --- pupy/modules/isearch.py | 103 +++++++++++++++++++++++++++ pupy/packages/windows/all/isearch.py | 47 ++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 pupy/modules/isearch.py create mode 100644 pupy/packages/windows/all/isearch.py diff --git a/pupy/modules/isearch.py b/pupy/modules/isearch.py new file mode 100644 index 00000000..c9c021d9 --- /dev/null +++ b/pupy/modules/isearch.py @@ -0,0 +1,103 @@ +# -*- encoding: utf-8 -*- + +from pupylib.PupyModule import config, PupyModule, PupyArgumentParser +from pupylib.PupyOutput import Table +from argparse import REMAINDER + +from datetime import datetime + +__class_name__='IndexSearchModule' + +# ZOMG Kill me please +def escape(x): + if "'" in x: + x = x.replace("'", "") + + return "'" + x + "'" + +@config(cat='gather', compat='windows') +class IndexSearchModule(PupyModule): + ''' Use Windows Search Index to search for data ''' + + dependencies = [ + 'win32com', 'win32api', 'winerror', + 'numbers', 'decimal', 'adodbapi', 'isearch' + ] + + @classmethod + def init_argparse(cls): + cls.arg_parser = PupyArgumentParser(prog='isearch', description=cls.__doc__) + cls.arg_parser.add_argument( + '-L', '--limit', type=int, help='Limit records (default 50)', + default=50) + cls.arg_parser.add_argument('-v', '--verbose', action='store_true', + default=False, help='Show SQL query') + cls.arg_parser.add_argument('-t', '--text', help='Text to search') + cls.arg_parser.add_argument('-p', '--path', help='Path to search') + cls.arg_parser.add_argument('-d', '--directory', help='Directory to limit output') + cls.arg_parser.add_argument( + '-R', '--raw', metavar='SELECT ... FROM SYSTEMINDEX ...', + nargs=REMAINDER, help='RAW SQL Query to search '\ + '(https://docs.microsoft.com/en-us/windows/'\ + 'desktop/search/-search-3x-advancedquerysyntax)') + + def run(self, args): + query = self.client.remote('isearch', 'query') + + request = [] + if args.raw: + request = args.raw + else: + request.append('SELECT TOP {} System.ItemUrl, System.Size, System.DateModified FROM SYSTEMINDEX'.format(args.limit)) + where = [] + if args.text: + where.append('FREETEXT({})'.format(escape(args.text))) + if args.directory: + where.append('SCOPE={}'.format(escape('file:'+args.directory))) + if args.path: + where.append('CONTAINS(System.FileName, {})'.format(escape(args.path))) + + if where: + request.append('WHERE') + request.append('AND'.join(where)) + + request.append('ORDER BY System.DateModified DESC') + + if not request: + self.error('You should specify request') + return + + text = ' '.join(request) + + if args.verbose: + self.info('QUERY: {}'.format(text)) + + idx, cidx, data, error = query(text, args.limit) + if error: + self.error(error) + elif not data: + self.warning('No data found') + else: + objects = [] + header = [] + legend = True + + if args.raw: + legend = False + for record in data: + objects.append({ + str(idx):v for idx,v in enumerate(record) + }) + header = [ + str(x) for x in xrange(cidx+1) + ] + else: + header = ['File', 'Size', 'Modified'] + for record in data: + objects.append({ + 'File': record[0][5:] if record[0].startswith('file:') else record[0], + 'Size': record[1], + 'Modified': datetime.fromtimestamp(record[2]) + }) + + self.log(Table(objects, header, legend=legend)) diff --git a/pupy/packages/windows/all/isearch.py b/pupy/packages/windows/all/isearch.py new file mode 100644 index 00000000..fcf528ee --- /dev/null +++ b/pupy/packages/windows/all/isearch.py @@ -0,0 +1,47 @@ +# -*- encoding: utf-8 -*- + +import adodbapi +import sys +import datetime + +PROVIDER = 'provider=Search.CollatorDSO.1;EXTENDED?PROPERTIES="Application=Windows"' + +def query(sql, limit): + data = [] + error = None + idx = 0 + cidx = 0 + + encoding = sys.getfilesystemencoding() + + conn = adodbapi.connect(PROVIDER) + + try: + cursor = conn.cursor() + cursor.execute(sql) + for idx, record in enumerate(cursor): + + if idx >= limit: + break + + line = [] + for cidx, column in enumerate(record): + if type(column) == str: + column = column.decode(encoding) + elif type(column) == datetime.datetime: + column = int(( + column - datetime.datetime.fromtimestamp(0) + ).total_seconds()) + line.append(column) + data.append(tuple(line)) + + except adodbapi.apibase.DatabaseError, e: + # ZOMG + parts = e.message.split('\n') + code = eval(parts[0])[1].decode(encoding) + error = '\n'.join(parts[1:]) + '\n' + code + + finally: + conn.close() + + return idx, cidx, data, error