#!/usr/bin/env python3
# Copyright (c) 2015 David Lundgren
#
# Python WMI Client
#
# Can be used in place of wmic when using check_wmi_plus.pl with Nagios.
#
# @author David Lundgren (@drlundgren)
#
# https://github.com/ProjectPatatoe/py-wmi-client/
#
# need additional packages:
# sudo pip3 install natsort
# sudo pip3 install impacket

import argparse
import re
import sys
import configparser

from natsort import natsorted, ns
from impacket.dcerpc.v5.dtypes import NULL
from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dcomrt import DCOMConnection

APP_VERSION = '0.1.0'


class WmiClient(object):
    """WMI Client"""

    def __init__(self, auth, host):
        """

        :param auth:
        :param host:
        """
        self.auth = auth
        self.host = host

    def get_language(self, lang):
        """
        Retrieve the language passed in from int to string

        :param lang: string
        :return:
        """
        if lang == 552:
            return 'en-US'
        return '??-??'

    def format_value(self, value, cimtype, type):
        """
        Formats the value based on the cimtype and type

        :param value:
        :param cimtype: string
        :param type: int
        :return:
        """
        if cimtype == 'string':
            if value == 0:
                return '(null)'
            else:
                return str(value).strip()
        elif cimtype == 'boolean':  # boolean
            if value == 'True':
                return 'True'
            else:
                return 'False'
        elif value is None:
            if cimtype == 'uint32' or cimtype == 'uint64':
                return '0'
        else:
            return ('%s' % value).strip()

    def print_results(self, queryObject, delimiter):
        """
        Prints the results in the classObject as wmic.c would

        :param queryObject: IEnumWbemClassObject
        :param delimiter: string
        :return:
        """

        while True:
            try:
                classObject = queryObject.Next(0xffffffff, 1)[0]
                print('CLASS: %s' % classObject.getClassName())
                record = classObject.getProperties()
                keys = []
                for name in record:
                    keys.append(name.strip())
                keys = natsorted(keys, alg=ns.IGNORECASE)
                print(delimiter.join(keys))
                tmp = []
                for key in keys:
                    if key == 'MUILanguages':
                        vals = []
                        for v in record[key]['value']:
                            vals.append(self.get_language(v))
                        record[key]['value'] = vals

                    if isinstance(record[key]['value'], list):
                        values = []
                        for v in record[key]['value']:
                            values.append(
                                self.format_value(v, record[key]['qualifiers']['CIMTYPE'], record[key]['type']))
                        tmp.append('(%s)' % ','.join(values))
                    else:
                        tmp.append('%s' % self.format_value(record[key]['value'], record[key]['qualifiers']['CIMTYPE'],
                                                            record[key]['type']))
                print(delimiter.join(tmp))
            except Exception as e:
                if e.get_error_code() != wmi.WBEMSTATUS.WBEM_S_FALSE:
                    raise
                else:
                    break

    def query_and_print(self, wql, **kwargs):
        """
        Querys and prints the results

        :param wql:
        :param kwargs:
        :return:
        """
        namespace = '//./root/cimv2'
        delimiter = '|'
        conn = None
        classObject = None
        wmiService = None
        wmiLogin = None

        if 'namespace' in kwargs:
            namespace = kwargs['namespace']
        if 'delimiter' in kwargs:
            delimiter = kwargs['delimiter']

        try:
            conn = DCOMConnection(self.host, self.auth['username'], self.auth['password'], self.auth['domain'], '', '',
                                  None, oxidResolver=True, doKerberos=False)
            wmiInterface = conn.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
            wmiLogin = wmi.IWbemLevel1Login(wmiInterface)
            wmiService = wmiLogin.NTLMLogin(namespace, NULL, NULL)
            wmiLogin.RemRelease()

            queryObject = wmiService.ExecQuery(wql.strip('\n'),
                                               wmi.WBEM_FLAG_RETURN_IMMEDIATELY | wmi.WBEM_FLAG_ENSURE_LOCATABLE)
            self.print_results(queryObject, delimiter)
            queryObject.RemRelease()

            wmiService.RemRelease()
            conn.disconnect()
        except Exception as e:
            if classObject is not None:
                classObject.RemRelease()
            if wmiLogin is not None:
                wmiLogin.RemRelease()
            if wmiService is not None:
                wmiService.RemRelease()
            if conn is not None:
                conn.disconnect()
            raise Exception("Could not connect to %s: %s" % (self.host, str(e)))


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="WMI client")
    parser.add_argument('-U', '--user', dest='user', help="[DOMAIN\]USERNAME[%%PASSWORD]")
    parser.add_argument('-A', '--authentication-file', dest='authfile', help="Authentication file")
    parser.add_argument('--delimiter', default='|', help="delimiter, default: |")
    parser.add_argument('--namespace', default='//./root/cimv2', help='namespace name (default //./root/cimv2)')
    parser.add_argument('host', metavar="//host")
    parser.add_argument('wql', metavar="query")

    options = parser.parse_args()

    auth = {
        'username': '',
        'password': '',
        'domain': ''
    }
    if options.authfile is not None:
        authfile = '[root]\n' + open(options.authfile, 'r').read()
        config = configparser.ConfigParser()
        config.read_string(authfile)
        auth['domain'] = config.get('root', 'domain', fallback='WORKGROUP')
        auth['username'] = config.get('root', 'username')
        auth['password'] = config.get('root', 'password')
    elif options.user is not None:
        auth['domain'], auth['username'], auth['password'] = re.compile(
            '(?:(?:([^/\\\\%]*)[/\\\\])?([^%]*))(?:%(.*))?').match(options.user).groups('')
    else:
        print("Missing user information")
        sys.exit(1)

    if auth['domain'] == '':
        auth['domain'] = 'WORKGROUP'

    WmiClient(auth, options.host[2:]).query_and_print(options.wql, namespace=options.namespace,
                                                      delimiter=options.delimiter)
