Blob Blame History Raw
diff --git a/README.hyperv b/README.hyperv
new file mode 100644
index 0000000..532f64e
--- /dev/null
+++ b/README.hyperv
@@ -0,0 +1,28 @@
+In order to make virt-who connection to Hyper-V work, some steps needs to be done first.
+
+1. Windows Remote Management must be enabled and HTTP or HTTPS listener must be running.
+
+    Following command can be used on Hyper-V server:
+
+        winrm quickconfig
+
+
+2. Firewall must allow Remote Administration
+
+    Following command can be used on Hyper-V server:
+
+        netsh advfirewall firewall set rule group="Remote Administration" new enable=yes
+
+
+3. Unencrypted connection must be enabled for HTTP (not required for HTTPS)
+
+    Following command can be used on Hyper-V server:
+
+        winrm set winrm/config/service @{AllowUnencrypted="true"}
+
+
+4. Only Basic and NTLM authentication methods are supported
+
+    Verify that at least one of methods Basic or Negotiate is enabled (True)
+
+        winrm get winrm/config/service/auth
diff --git a/hyperv.py b/hyperv.py
new file mode 100644
index 0000000..2fcb1a3
--- /dev/null
+++ b/hyperv.py
@@ -0,0 +1,292 @@
+
+import sys
+import httplib
+import urlparse
+import base64
+from uuid import uuid1
+
+# Import XML parser
+try:
+    from elementtree import ElementTree
+except ImportError:
+    from xml.etree import ElementTree
+
+import ntlm
+
+NAMESPACES = {
+    's':     'http://www.w3.org/2003/05/soap-envelope',
+    'wsa':   'http://schemas.xmlsoap.org/ws/2004/08/addressing',
+    'wsman': 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd',
+    'wsen':  'http://schemas.xmlsoap.org/ws/2004/09/enumeration'
+}
+
+ENVELOPE = """<?xml version="1.0" encoding="UTF-8"?>
+<s:Envelope """ + " ".join(('xmlns:%s="%s"' % (k, v) for k, v in NAMESPACES.items())) + """>
+    %s
+    %s
+</s:Envelope>"""
+
+def getHeader(action):
+    return """<s:Header>
+        <wsa:Action s:mustUnderstand="true">""" + NAMESPACES['wsen'] + "/" + action + """</wsa:Action>
+        <wsa:To s:mustUnderstand="true">%(url)s</wsa:To>
+        <wsman:ResourceURI s:mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/wmi/%(namespace)s/*</wsman:ResourceURI>
+        <wsa:MessageID s:mustUnderstand="true">uuid:""" + str(uuid1()) + """</wsa:MessageID>
+        <wsa:ReplyTo>
+            <wsa:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address>
+        </wsa:ReplyTo>
+    </s:Header>"""
+
+ENUMERATE_BODY = """<s:Body>
+        <wsen:Enumerate>
+            <wsman:Filter Dialect="http://schemas.microsoft.com/wbem/wsman/1/WQL">%(query)s</wsman:Filter>
+        </wsen:Enumerate>
+    </s:Body>"""
+
+PULL_BODY = """<s:Body>
+        <wsen:Pull>
+            <wsen:EnumerationContext>%(EnumerationContext)s</wsen:EnumerationContext>
+        </wsen:Pull>
+    </s:Body>"""
+
+ENUMERATE_XML = ENVELOPE % (getHeader("Enumerate"), ENUMERATE_BODY)
+PULL_XML = ENVELOPE % (getHeader("Pull"), PULL_BODY)
+
+class HyperVSoap(object):
+    def __init__(self, url, connection, headers):
+        self.url = url
+        self.connection = connection
+        self.headers = headers
+
+    def post(self, body):
+        self.headers["Content-Length"] = "%d" % len(body)
+        self.connection.request("POST", self.url, body=body, headers=self.headers)
+        response = self.connection.getresponse()
+        if response.status == 401:
+            raise HyperVAuthFailed("Authentication failed")
+        if response.status != 200:
+            raise HyperVException("Communication with Hyper-V failed, HTTP error: %d" % response.status)
+        if response is None:
+            raise HyperVException("No reply from Hyper-V")
+        return response
+
+    @classmethod
+    def _Instance(cls, xml):
+        def stripNamespace(tag):
+            return tag[tag.find("}") + 1:]
+        children = xml.getchildren()
+        if len(children) < 1:
+            return None
+        child = children[0]
+        properties = {}
+        for ch in child.getchildren():
+            properties[stripNamespace(ch.tag)] = ch.text
+        return properties
+
+    def Enumerate(self, query, namespace="root/virtualization"):
+        data = ENUMERATE_XML % { 'url': self.url, 'query': query, 'namespace': namespace }
+        response = self.post(data)
+        d = response.read()
+        xml = ElementTree.fromstring(d)
+        if xml.tag != "{%(s)s}Envelope" % NAMESPACES:
+            raise HyperVException("Wrong reply format")
+        responses = xml.findall("{%(s)s}Body/{%(wsen)s}EnumerateResponse" % NAMESPACES)
+        if len(responses) < 1:
+            raise HyperVException("Wrong reply format")
+        contexts = responses[0].getchildren()
+        if len(contexts) < 1:
+            raise HyperVException("Wrong reply format")
+
+        if contexts[0].tag != "{%(wsen)s}EnumerationContext" % NAMESPACES:
+            raise HyperVException("Wrong reply format")
+        return contexts[0].text
+
+    def _PullOne(self, uuid, namespace):
+        data = PULL_XML % { 'url': self.url, 'EnumerationContext': uuid, 'namespace': namespace }
+        response = self.post(data)
+        d = response.read()
+        xml = ElementTree.fromstring(d)
+        if xml.tag != "{%(s)s}Envelope" % NAMESPACES:
+            raise HyperVException("Wrong reply format")
+        responses = xml.findall("{%(s)s}Body/{%(wsen)s}PullResponse" % NAMESPACES)
+        if len(responses) < 0:
+            raise HyperVException("Wrong reply format")
+
+        uuid = None
+        instance = None
+
+        for node in responses[0].getchildren():
+            if node.tag == "{%(wsen)s}EnumerationContext" % NAMESPACES:
+                uuid = node.text
+            elif node.tag == "{%(wsen)s}Items" % NAMESPACES:
+                instance = HyperVSoap._Instance(node)
+
+        return uuid, instance
+
+    def Pull(self, uuid, namespace="root/virtualization"):
+        instances = []
+        while uuid is not None:
+            uuid, instance = self._PullOne(uuid, namespace)
+            if instance is not None:
+                instances.append(instance)
+        return instances
+
+
+class HyperVException(Exception):
+    pass
+
+class HyperVAuthFailed(HyperVException):
+    pass
+
+
+class HyperV:
+    def __init__(self, logger, url, username, password):
+        self.logger = logger
+        self.username = username
+        self.password = password
+
+        # Parse URL and create proper one
+        if "//" not in url:
+            url = "//" + url
+        parsed = urlparse.urlsplit(url, "http")
+        if ":" not in parsed[1]:
+            if parsed[0] == "https":
+                self.host = parsed[1] + ":5986"
+            else:
+                self.host = parsed[1] + ":5985"
+        else:
+            self.host = parsed[1]
+        if parsed[2] == "":
+            path = "wsman"
+        else:
+            path = parsed[2]
+        self.url = urlparse.urlunsplit((parsed[0], self.host, path, "", ""))
+
+        logger.debug("Hyper-V url: %s" % self.url)
+
+        # Check if we have domain defined and set flags accordingly
+        user_parts = username.split('\\', 1)
+        if len(user_parts) == 1:
+            self.username = user_parts[0]
+            self.domainname = ''
+            self.type1_flags = ntlm.NTLM_TYPE1_FLAGS & ~ntlm.NTLM_NegotiateOemDomainSupplied
+        else:
+            self.domainname = user_parts[0].upper()
+            self.username = user_parts[1]
+            self.type1_flags = ntlm.NTLM_TYPE1_FLAGS
+
+    def connect(self):
+        if self.url.startswith("https"):
+            connection = httplib.HTTPSConnection(self.host)
+        else:
+            connection = httplib.HTTPConnection(self.host)
+
+        headers = {}
+        headers["Connection"] = "Keep-Alive"
+        headers["Content-Length"] = "0"
+
+        connection.request("POST", self.url, headers=headers)
+        response = connection.getresponse()
+        response.read()
+        if response.status == 200:
+            return connection, headers
+        elif response.status == 404:
+            raise HyperVException("Invalid HyperV url: %s" % self.url)
+        elif response.status != 401:
+            raise HyperVException("Unable to connect to HyperV at: %s" % self.url)
+        # 401 - need authentication
+
+        authenticate_header = response.getheader("WWW-Authenticate", "")
+        if 'Negotiate' in authenticate_header:
+            try:
+                self.ntlmAuth(connection, headers)
+            except HyperVAuthFailed:
+                if 'Basic' in authenticate_header:
+                    self.basicAuth(connection, headers)
+                else:
+                    raise
+        elif 'Basic' in authenticate_header:
+            self.basicAuth(connection, headers)
+        else:
+            raise HyperVAuthFailed("Server doesn't known any supported authentication method")
+        return connection, headers
+
+    def ntlmAuth(self, connection, headers):
+        self.logger.debug("Using NTLM authentication")
+        # Use ntlm
+        headers["Authorization"] = "Negotiate %s" % ntlm.create_NTLM_NEGOTIATE_MESSAGE(self.username, self.type1_flags)
+
+        connection.request("POST", self.url, headers=headers)
+        response = connection.getresponse()
+        response.read()
+        if response.status != 401:
+            raise HyperVAuthFailed("NTLM negotiation failed")
+
+        auth_header = response.getheader("WWW-Authenticate", "")
+        if auth_header == "":
+            raise HyperVAuthFailed("NTLM negotiation failed")
+
+        nego, challenge = auth_header.split(" ")
+        if nego != "Negotiate":
+            print >>sys.stderr, "Wrong header: ", auth_header
+            sys.exit(1)
+
+        nonce, flags = ntlm.parse_NTLM_CHALLENGE_MESSAGE(challenge)
+        headers["Authorization"] = "Negotiate %s" % ntlm.create_NTLM_AUTHENTICATE_MESSAGE(nonce, self.username, self.domainname, self.password, flags)
+
+        connection.request("POST", self.url, headers=headers)
+        response = connection.getresponse()
+        response.read()
+        if response.status == 200:
+            headers.pop("Authorization")
+            self.logger.debug("NTLM authentication successful")
+        else:
+            raise HyperVAuthFailed("NTLM negotiation failed")
+
+    def basicAuth(self, connection, headers):
+        self.logger.debug("Using Basic authentication")
+
+        passphrase = "%s:%s" % (self.username, self.password)
+        encoded = base64.encodestring(passphrase)
+        headers["Authorization"] = "Basic %s" % encoded.replace('\n', '')
+
+    @classmethod
+    def decodeWinUUID(cls, uuid):
+        """ Windows UUID needs to be decoded using following key
+        From: {78563412-AB90-EFCD-1234-567890ABCDEF}
+        To:    12345678-90AB-CDEF-1234-567890ABCDEF
+        """
+        if uuid[0] == "{":
+            s = uuid[1:-1]
+        else:
+            s = uuid
+        return s[6:8] + s[4:6] + s[2:4] + s[0:2] + "-" + s[11:13] + s[9:11] + "-" + s[16:18] + s[14:16] + s[18:]
+
+    def getHostGuestMapping(self):
+        guests = []
+        connection, headers = self.connect()
+        hypervsoap = HyperVSoap(self.url, connection, headers)
+        # SettingType == 3 means current setting, 5 is snapshot - we don't want snapshots
+        uuid = hypervsoap.Enumerate("select BIOSGUID from Msvm_VirtualSystemSettingData where SettingType = 3")
+        for instance in hypervsoap.Pull(uuid):
+            guests.append(HyperV.decodeWinUUID(instance["BIOSGUID"]))
+        uuid = hypervsoap.Enumerate("select UUID from Win32_ComputerSystemProduct", "root/cimv2")
+        host = None
+        for instance in hypervsoap.Pull(uuid, "root/cimv2"):
+            host = HyperV.decodeWinUUID(instance["UUID"])
+        return { host: guests }
+
+    def ping(self):
+        return True
+
+if __name__ == '__main__':
+    # TODO: read from config
+    if len(sys.argv) < 4:
+        print "Usage: %s url username password"
+        sys.exit(0)
+
+    import logging
+    logger = logging.Logger("")
+    logger.addHandler(logging.StreamHandler())
+    hyperv = HyperV(logger, sys.argv[1], sys.argv[2], sys.argv[3])
+    print hyperv.getHostGuestMapping()
diff --git a/ntlm.py b/ntlm.py
new file mode 100644
index 0000000..d950611
--- /dev/null
+++ b/ntlm.py
@@ -0,0 +1,494 @@
+# This library is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library.  If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
+
+import struct
+import base64
+import string
+import hashlib
+import hmac
+import random
+from socket import gethostname
+
+import M2Crypto
+
+
+# DES handling functions
+
+def des_encrypt(key_str, plain_text):
+    k = str_to_key56(key_str)
+    k = key56_to_key64(k)
+    key_str = ''
+    for i in k:
+        key_str += chr(i & 0xFF)
+    des = M2Crypto.EVP.Cipher("des_ecb", key=key_str, op=M2Crypto.encrypt, iv='\0'*16)
+
+    return des.update(plain_text)
+
+def str_to_key56(key_str):
+    if len(key_str) < 7:
+        key_str = key_str + '\000\000\000\000\000\000\000'[:(7 - len(key_str))]
+    key_56 = []
+    for i in key_str[:7]: key_56.append(ord(i))
+
+    return key_56
+
+def key56_to_key64(key_56):
+    ""
+    key = []
+    for i in range(8): key.append(0)
+
+    key[0] = key_56[0];
+    key[1] = ((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1);
+    key[2] = ((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2);
+    key[3] = ((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3);
+    key[4] = ((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4);
+    key[5] = ((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5);
+    key[6] = ((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6);
+    key[7] =  (key_56[6] << 1) & 0xFF;
+
+    key = set_key_odd_parity(key)
+
+    return key
+
+def set_key_odd_parity(key):
+    ""
+    for i in range(len(key)):
+        for k in range(7):
+            bit = 0
+            t = key[i] >> k
+            bit = (t ^ bit) & 0x1
+        key[i] = (key[i] & 0xFE) | bit
+
+    return key
+
+# NTLM implemntation
+
+NTLM_NegotiateUnicode                =  0x00000001
+NTLM_NegotiateOEM                    =  0x00000002
+NTLM_RequestTarget                   =  0x00000004
+NTLM_Unknown9                        =  0x00000008
+NTLM_NegotiateSign                   =  0x00000010
+NTLM_NegotiateSeal                   =  0x00000020
+NTLM_NegotiateDatagram               =  0x00000040
+NTLM_NegotiateLanManagerKey          =  0x00000080
+NTLM_Unknown8                        =  0x00000100
+NTLM_NegotiateNTLM                   =  0x00000200
+NTLM_NegotiateNTOnly                 =  0x00000400
+NTLM_Anonymous                       =  0x00000800
+NTLM_NegotiateOemDomainSupplied      =  0x00001000
+NTLM_NegotiateOemWorkstationSupplied =  0x00002000
+NTLM_Unknown6                        =  0x00004000
+NTLM_NegotiateAlwaysSign             =  0x00008000
+NTLM_TargetTypeDomain                =  0x00010000
+NTLM_TargetTypeServer                =  0x00020000
+NTLM_TargetTypeShare                 =  0x00040000
+NTLM_NegotiateExtendedSecurity       =  0x00080000
+NTLM_NegotiateIdentify               =  0x00100000
+NTLM_Unknown5                        =  0x00200000
+NTLM_RequestNonNTSessionKey          =  0x00400000
+NTLM_NegotiateTargetInfo             =  0x00800000
+NTLM_Unknown4                        =  0x01000000
+NTLM_NegotiateVersion                =  0x02000000
+NTLM_Unknown3                        =  0x04000000
+NTLM_Unknown2                        =  0x08000000
+NTLM_Unknown1                        =  0x10000000
+NTLM_Negotiate128                    =  0x20000000
+NTLM_NegotiateKeyExchange            =  0x40000000
+NTLM_Negotiate56                     =  0x80000000
+
+# we send these flags with our type 1 message
+NTLM_TYPE1_FLAGS = (NTLM_NegotiateUnicode | \
+                    NTLM_NegotiateOEM | \
+                    NTLM_RequestTarget | \
+                    NTLM_NegotiateNTLM | \
+                    NTLM_NegotiateOemDomainSupplied | \
+                    NTLM_NegotiateOemWorkstationSupplied | \
+                    NTLM_NegotiateAlwaysSign | \
+                    NTLM_NegotiateExtendedSecurity | \
+                    NTLM_NegotiateVersion | \
+                    NTLM_Negotiate128 | \
+                    NTLM_Negotiate56 )
+NTLM_TYPE2_FLAGS = (NTLM_NegotiateUnicode | \
+                    NTLM_RequestTarget | \
+                    NTLM_NegotiateNTLM | \
+                    NTLM_NegotiateAlwaysSign | \
+                    NTLM_NegotiateExtendedSecurity | \
+                    NTLM_NegotiateTargetInfo | \
+                    NTLM_NegotiateVersion | \
+                    NTLM_Negotiate128 | \
+                    NTLM_Negotiate56)
+
+NTLM_MsvAvEOL             = 0 # Indicates that this is the last AV_PAIR in the list. AvLen MUST be 0. This type of information MUST be present in the AV pair list.
+NTLM_MsvAvNbComputerName  = 1 # The server's NetBIOS computer name. The name MUST be in Unicode, and is not null-terminated. This type of information MUST be present in the AV_pair list.
+NTLM_MsvAvNbDomainName    = 2 # The server's NetBIOS domain name. The name MUST be in Unicode, and is not null-terminated. This type of information MUST be present in the AV_pair list.
+NTLM_MsvAvDnsComputerName = 3 # The server's Active Directory DNS computer name. The name MUST be in Unicode, and is not null-terminated.
+NTLM_MsvAvDnsDomainName   = 4 # The server's Active Directory DNS domain name. The name MUST be in Unicode, and is not null-terminated.
+NTLM_MsvAvDnsTreeName     = 5 # The server's Active Directory (AD) DNS forest tree name. The name MUST be in Unicode, and is not null-terminated.
+NTLM_MsvAvFlags           = 6 # A field containing a 32-bit value indicating server or client configuration. 0x00000001: indicates to the client that the account authentication is constrained. 0x00000002: indicates that the client is providing message integrity in the MIC field (section 2.2.1.3) in the AUTHENTICATE_MESSAGE.
+NTLM_MsvAvTimestamp       = 7 # A FILETIME structure ([MS-DTYP] section 2.3.1) in little-endian byte order that contains the server local time.<12>
+NTLM_MsAvRestrictions     = 8 #A Restriction_Encoding structure (section 2.2.2.2). The Value field contains a structure representing the integrity level of the security principal, as well as a MachineID created at computer startup to identify the calling machine. <13>
+
+
+"""
+utility functions for Microsoft NTLM authentication
+
+References:
+[MS-NLMP]: NT LAN Manager (NTLM) Authentication Protocol Specification
+http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/%5BMS-NLMP%5D.pdf
+
+[MS-NTHT]: NTLM Over HTTP Protocol Specification
+http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/%5BMS-NTHT%5D.pdf
+
+Cntlm Authentication Proxy
+http://cntlm.awk.cz/
+
+NTLM Authorization Proxy Server
+http://sourceforge.net/projects/ntlmaps/
+
+Optimized Attack for NTLM2 Session Response
+http://www.blackhat.com/presentations/bh-asia-04/bh-jp-04-pdfs/bh-jp-04-seki.pdf
+"""
+def dump_NegotiateFlags(NegotiateFlags):
+    if NegotiateFlags & NTLM_NegotiateUnicode:
+        print "NTLM_NegotiateUnicode set"
+    if NegotiateFlags & NTLM_NegotiateOEM:
+        print "NTLM_NegotiateOEM set"
+    if NegotiateFlags & NTLM_RequestTarget:
+        print "NTLM_RequestTarget set"
+    if NegotiateFlags & NTLM_Unknown9:
+        print "NTLM_Unknown9 set"
+    if NegotiateFlags & NTLM_NegotiateSign:
+        print "NTLM_NegotiateSign set"
+    if NegotiateFlags & NTLM_NegotiateSeal:
+        print "NTLM_NegotiateSeal set"
+    if NegotiateFlags & NTLM_NegotiateDatagram:
+        print "NTLM_NegotiateDatagram set"
+    if NegotiateFlags & NTLM_NegotiateLanManagerKey:
+        print "NTLM_NegotiateLanManagerKey set"
+    if NegotiateFlags & NTLM_Unknown8:
+        print "NTLM_Unknown8 set"
+    if NegotiateFlags & NTLM_NegotiateNTLM:
+        print "NTLM_NegotiateNTLM set"
+    if NegotiateFlags & NTLM_NegotiateNTOnly:
+        print "NTLM_NegotiateNTOnly set"
+    if NegotiateFlags & NTLM_Anonymous:
+        print "NTLM_Anonymous set"
+    if NegotiateFlags & NTLM_NegotiateOemDomainSupplied:
+        print "NTLM_NegotiateOemDomainSupplied set"
+    if NegotiateFlags & NTLM_NegotiateOemWorkstationSupplied:
+        print "NTLM_NegotiateOemWorkstationSupplied set"
+    if NegotiateFlags & NTLM_Unknown6:
+        print "NTLM_Unknown6 set"
+    if NegotiateFlags & NTLM_NegotiateAlwaysSign:
+        print "NTLM_NegotiateAlwaysSign set"
+    if NegotiateFlags & NTLM_TargetTypeDomain:
+        print "NTLM_TargetTypeDomain set"
+    if NegotiateFlags & NTLM_TargetTypeServer:
+        print "NTLM_TargetTypeServer set"
+    if NegotiateFlags & NTLM_TargetTypeShare:
+        print "NTLM_TargetTypeShare set"
+    if NegotiateFlags & NTLM_NegotiateExtendedSecurity:
+        print "NTLM_NegotiateExtendedSecurity set"
+    if NegotiateFlags & NTLM_NegotiateIdentify:
+        print "NTLM_NegotiateIdentify set"
+    if NegotiateFlags & NTLM_Unknown5:
+        print "NTLM_Unknown5 set"
+    if NegotiateFlags & NTLM_RequestNonNTSessionKey:
+        print "NTLM_RequestNonNTSessionKey set"
+    if NegotiateFlags & NTLM_NegotiateTargetInfo:
+        print "NTLM_NegotiateTargetInfo set"
+    if NegotiateFlags & NTLM_Unknown4:
+        print "NTLM_Unknown4 set"
+    if NegotiateFlags & NTLM_NegotiateVersion:
+        print "NTLM_NegotiateVersion set"
+    if NegotiateFlags & NTLM_Unknown3:
+        print "NTLM_Unknown3 set"
+    if NegotiateFlags & NTLM_Unknown2:
+        print "NTLM_Unknown2 set"
+    if NegotiateFlags & NTLM_Unknown1:
+        print "NTLM_Unknown1 set"
+    if NegotiateFlags & NTLM_Negotiate128:
+        print "NTLM_Negotiate128 set"
+    if NegotiateFlags & NTLM_NegotiateKeyExchange:
+        print "NTLM_NegotiateKeyExchange set"
+    if NegotiateFlags & NTLM_Negotiate56:
+        print "NTLM_Negotiate56 set"
+
+def create_NTLM_NEGOTIATE_MESSAGE(user, type1_flags=NTLM_TYPE1_FLAGS):
+    BODY_LENGTH = 40
+    Payload_start = BODY_LENGTH # in bytes
+    protocol = 'NTLMSSP\0'    #name
+
+    type = struct.pack('<I',1) #type 1
+
+    flags =  struct.pack('<I', type1_flags)
+    Workstation = gethostname().upper().encode('ascii')
+    user_parts = user.split('\\', 1)
+    DomainName = user_parts[0].upper().encode('ascii')
+    EncryptedRandomSessionKey = ""
+
+    WorkstationLen = struct.pack('<H', len(Workstation))
+    WorkstationMaxLen = struct.pack('<H', len(Workstation))
+    WorkstationBufferOffset = struct.pack('<I', Payload_start)
+    Payload_start += len(Workstation)
+    DomainNameLen = struct.pack('<H', len(DomainName))
+    DomainNameMaxLen = struct.pack('<H', len(DomainName))
+    DomainNameBufferOffset = struct.pack('<I',Payload_start)
+    Payload_start += len(DomainName)
+    ProductMajorVersion = struct.pack('<B', 5)
+    ProductMinorVersion = struct.pack('<B', 1)
+    ProductBuild = struct.pack('<H', 2600)
+    VersionReserved1 = struct.pack('<B', 0)
+    VersionReserved2 = struct.pack('<B', 0)
+    VersionReserved3 = struct.pack('<B', 0)
+    NTLMRevisionCurrent = struct.pack('<B', 15)
+
+    msg1 = protocol + type + flags + \
+            DomainNameLen + DomainNameMaxLen + DomainNameBufferOffset + \
+            WorkstationLen + WorkstationMaxLen + WorkstationBufferOffset + \
+            ProductMajorVersion + ProductMinorVersion + ProductBuild + \
+            VersionReserved1 + VersionReserved2 + VersionReserved3 + NTLMRevisionCurrent
+    assert BODY_LENGTH==len(msg1), "BODY_LENGTH: %d != msg1: %d" % (BODY_LENGTH,len(msg1))
+    msg1 += Workstation + DomainName
+    msg1 = base64.encodestring(msg1)
+    msg1 = string.replace(msg1, '\n', '')
+    return msg1
+
+def parse_NTLM_CHALLENGE_MESSAGE(msg2):
+    ""
+    msg2 = base64.decodestring(msg2)
+    Signature = msg2[0:8]
+    msg_type = struct.unpack("<I",msg2[8:12])[0]
+    assert(msg_type==2)
+    TargetNameLen = struct.unpack("<H",msg2[12:14])[0]
+    TargetNameMaxLen = struct.unpack("<H",msg2[14:16])[0]
+    TargetNameOffset = struct.unpack("<I",msg2[16:20])[0]
+    TargetName = msg2[TargetNameOffset:TargetNameOffset+TargetNameMaxLen]
+    NegotiateFlags = struct.unpack("<I",msg2[20:24])[0]
+    ServerChallenge = msg2[24:32]
+    Reserved = msg2[32:40]
+    TargetInfoLen = struct.unpack("<H",msg2[40:42])[0]
+    TargetInfoMaxLen = struct.unpack("<H",msg2[42:44])[0]
+    TargetInfoOffset = struct.unpack("<I",msg2[44:48])[0]
+    TargetInfo = msg2[TargetInfoOffset:TargetInfoOffset+TargetInfoLen]
+    i=0
+    TimeStamp = '\0'*8
+    while(i<TargetInfoLen):
+        AvId = struct.unpack("<H",TargetInfo[i:i+2])[0]
+        AvLen = struct.unpack("<H",TargetInfo[i+2:i+4])[0]
+        AvValue = TargetInfo[i+4:i+4+AvLen]
+        i = i+4+AvLen
+        if AvId == NTLM_MsvAvTimestamp:
+            TimeStamp = AvValue
+        #~ print AvId, AvValue.decode('utf-16')
+    return (ServerChallenge, NegotiateFlags)
+
+def create_NTLM_AUTHENTICATE_MESSAGE(nonce, user, domain, password, NegotiateFlags):
+    ""
+    is_unicode  = NegotiateFlags & NTLM_NegotiateUnicode
+    is_NegotiateExtendedSecurity = NegotiateFlags & NTLM_NegotiateExtendedSecurity
+
+    flags =  struct.pack('<I',NTLM_TYPE2_FLAGS)
+
+    BODY_LENGTH = 72
+    Payload_start = BODY_LENGTH # in bytes
+
+    Workstation = gethostname().upper()
+    DomainName = domain.upper()
+    UserName = user
+    EncryptedRandomSessionKey = ""
+    if is_unicode:
+        Workstation = Workstation.encode('utf-16-le')
+        DomainName = DomainName.encode('utf-16-le')
+        UserName = UserName.encode('utf-16-le')
+        EncryptedRandomSessionKey = EncryptedRandomSessionKey.encode('utf-16-le')
+    LmChallengeResponse = calc_resp(create_LM_hashed_password_v1(password), nonce)
+    NtChallengeResponse = calc_resp(create_NT_hashed_password_v1(password), nonce)
+
+    if is_NegotiateExtendedSecurity:
+        pwhash = create_NT_hashed_password_v1(password, UserName, DomainName)
+        ClientChallenge = ""
+        for i in range(8):
+           ClientChallenge+= chr(random.getrandbits(8))
+        (NtChallengeResponse, LmChallengeResponse) = ntlm2sr_calc_resp(pwhash, nonce, ClientChallenge) #='\x39 e3 f4 cd 59 c5 d8 60')
+    Signature = 'NTLMSSP\0'
+    MessageType = struct.pack('<I',3)  #type 3
+
+    DomainNameLen = struct.pack('<H', len(DomainName))
+    DomainNameMaxLen = struct.pack('<H', len(DomainName))
+    DomainNameOffset = struct.pack('<I', Payload_start)
+    Payload_start += len(DomainName)
+
+    UserNameLen = struct.pack('<H', len(UserName))
+    UserNameMaxLen = struct.pack('<H', len(UserName))
+    UserNameOffset = struct.pack('<I', Payload_start)
+    Payload_start += len(UserName)
+
+    WorkstationLen = struct.pack('<H', len(Workstation))
+    WorkstationMaxLen = struct.pack('<H', len(Workstation))
+    WorkstationOffset = struct.pack('<I', Payload_start)
+    Payload_start += len(Workstation)
+
+    LmChallengeResponseLen = struct.pack('<H', len(LmChallengeResponse))
+    LmChallengeResponseMaxLen = struct.pack('<H', len(LmChallengeResponse))
+    LmChallengeResponseOffset = struct.pack('<I', Payload_start)
+    Payload_start += len(LmChallengeResponse)
+
+    NtChallengeResponseLen = struct.pack('<H', len(NtChallengeResponse))
+    NtChallengeResponseMaxLen = struct.pack('<H', len(NtChallengeResponse))
+    NtChallengeResponseOffset = struct.pack('<I', Payload_start)
+    Payload_start += len(NtChallengeResponse)
+
+    EncryptedRandomSessionKeyLen = struct.pack('<H', len(EncryptedRandomSessionKey))
+    EncryptedRandomSessionKeyMaxLen = struct.pack('<H', len(EncryptedRandomSessionKey))
+    EncryptedRandomSessionKeyOffset = struct.pack('<I',Payload_start)
+    Payload_start +=  len(EncryptedRandomSessionKey)
+    NegotiateFlags = flags
+
+    ProductMajorVersion = struct.pack('<B', 5)
+    ProductMinorVersion = struct.pack('<B', 1)
+    ProductBuild = struct.pack('<H', 2600)
+    VersionReserved1 = struct.pack('<B', 0)
+    VersionReserved2 = struct.pack('<B', 0)
+    VersionReserved3 = struct.pack('<B', 0)
+    NTLMRevisionCurrent = struct.pack('<B', 15)
+
+    MIC = struct.pack('<IIII',0,0,0,0)
+    msg3 = Signature + MessageType + \
+            LmChallengeResponseLen + LmChallengeResponseMaxLen + LmChallengeResponseOffset + \
+            NtChallengeResponseLen + NtChallengeResponseMaxLen + NtChallengeResponseOffset + \
+            DomainNameLen + DomainNameMaxLen + DomainNameOffset + \
+            UserNameLen + UserNameMaxLen + UserNameOffset + \
+            WorkstationLen + WorkstationMaxLen + WorkstationOffset + \
+            EncryptedRandomSessionKeyLen + EncryptedRandomSessionKeyMaxLen + EncryptedRandomSessionKeyOffset + \
+            NegotiateFlags + \
+            ProductMajorVersion + ProductMinorVersion + ProductBuild + \
+            VersionReserved1 + VersionReserved2 + VersionReserved3 + NTLMRevisionCurrent
+    assert BODY_LENGTH==len(msg3), "BODY_LENGTH: %d != msg3: %d" % (BODY_LENGTH,len(msg3))
+    Payload = DomainName + UserName + Workstation + LmChallengeResponse + NtChallengeResponse + EncryptedRandomSessionKey
+    msg3 += Payload
+    msg3 = base64.encodestring(msg3)
+    msg3 = string.replace(msg3, '\n', '')
+    return msg3
+
+def calc_resp(password_hash, server_challenge):
+    """calc_resp generates the LM response given a 16-byte password hash and the
+        challenge from the Type-2 message.
+        @param password_hash
+            16-byte password hash
+        @param server_challenge
+            8-byte challenge from Type-2 message
+        returns
+            24-byte buffer to contain the LM response upon return
+    """
+    # padding with zeros to make the hash 21 bytes long
+    password_hash = password_hash + '\0' * (21 - len(password_hash))
+    return des_encrypt(password_hash[ 0: 7], server_challenge[0:8]) + \
+           des_encrypt(password_hash[ 7:14], server_challenge[0:8]) + \
+           des_encrypt(password_hash[14:21], server_challenge[0:8])
+
+def ComputeResponse(ResponseKeyNT, ResponseKeyLM, ServerChallenge, ServerName, ClientChallenge='\xaa'*8, Time='\0'*8):
+    LmChallengeResponse = hmac.new(ResponseKeyLM, ServerChallenge+ClientChallenge).digest() + ClientChallenge
+
+    Responserversion = '\x01'
+    HiResponserversion = '\x01'
+    temp = Responserversion + HiResponserversion + '\0'*6 + Time + ClientChallenge + '\0'*4 + ServerChallenge + '\0'*4
+    NTProofStr  = hmac.new(ResponseKeyNT, ServerChallenge + temp).digest()
+    NtChallengeResponse = NTProofStr + temp
+
+    SessionBaseKey = hmac.new(ResponseKeyNT, NTProofStr).digest()
+    return (NtChallengeResponse, LmChallengeResponse)
+
+def ntlm2sr_calc_resp(ResponseKeyNT, ServerChallenge, ClientChallenge='\xaa'*8):
+    LmChallengeResponse = ClientChallenge + '\0'*16
+    sess = hashlib.md5(ServerChallenge+ClientChallenge).digest()
+    NtChallengeResponse = calc_resp(ResponseKeyNT, sess[0:8])
+    return (NtChallengeResponse, LmChallengeResponse)
+
+def create_LM_hashed_password_v1(passwd):
+    "setup LanManager password"
+    "create LanManager hashed password"
+
+    # fix the password length to 14 bytes
+    passwd = string.upper(passwd)
+    lm_pw = passwd + '\0' * (14 - len(passwd))
+    lm_pw = passwd[0:14]
+
+    # do hash
+    magic_str = "KGS!@#$%" # page 57 in [MS-NLMP]
+
+    return des_encrypt(lm_pw[0:7], magic_str) + des_encrypt(lm_pw[7:14], magic_str)
+
+def create_NT_hashed_password_v1(passwd, user=None, domain=None):
+    "create NT hashed password"
+    digest = hashlib.new('md4', passwd.encode('utf-16le')).digest()
+    return digest
+
+def create_NT_hashed_password_v2(passwd, user, domain):
+    "create NT hashed password"
+    digest = create_NT_hashed_password_v1(passwd)
+
+    return hmac.new(digest, (user.upper()+domain).encode('utf-16le')).digest()
+    return digest
+
+def create_sessionbasekey(password):
+    return hashlib.new('md4', create_NT_hashed_password_v1(password)).digest()
+
+if __name__ == "__main__":
+    def ByteToHex( byteStr ):
+        """
+        Convert a byte string to it's hex string representation e.g. for output.
+        """
+        return ' '.join( [ "%02X" % ord( x ) for x in byteStr ] )
+
+    def HexToByte( hexStr ):
+        """
+        Convert a string hex byte values into a byte string. The Hex Byte values may
+        or may not be space separated.
+        """
+        bytes = []
+
+        hexStr = ''.join( hexStr.split(" ") )
+
+        for i in range(0, len(hexStr), 2):
+            bytes.append( chr( int (hexStr[i:i+2], 16 ) ) )
+
+        return ''.join( bytes )
+
+    ServerChallenge = HexToByte("01 23 45 67 89 ab cd ef")
+    ClientChallenge = '\xaa'*8
+    Time = '\x00'*8
+    Workstation = "COMPUTER".encode('utf-16-le')
+    ServerName = "Server".encode('utf-16-le')
+    User = "User"
+    Domain = "Domain"
+    Password = "Password"
+    RandomSessionKey = '\55'*16
+    assert HexToByte("e5 2c ac 67 41 9a 9a 22 4a 3b 10 8f 3f a6 cb 6d") == create_LM_hashed_password_v1(Password)                  # [MS-NLMP] page 72
+    assert HexToByte("a4 f4 9c 40 65 10 bd ca b6 82 4e e7 c3 0f d8 52") == create_NT_hashed_password_v1(Password)    # [MS-NLMP] page 73
+    assert HexToByte("d8 72 62 b0 cd e4 b1 cb 74 99 be cc cd f1 07 84") == create_sessionbasekey(Password)
+    assert HexToByte("67 c4 30 11 f3 02 98 a2 ad 35 ec e6 4f 16 33 1c 44 bd be d9 27 84 1f 94") == calc_resp(create_NT_hashed_password_v1(Password), ServerChallenge)
+    assert HexToByte("98 de f7 b8 7f 88 aa 5d af e2 df 77 96 88 a1 72 de f1 1c 7d 5c cd ef 13") == calc_resp(create_LM_hashed_password_v1(Password), ServerChallenge)
+
+    (NTLMv1Response,LMv1Response) = ntlm2sr_calc_resp(create_NT_hashed_password_v1(Password), ServerChallenge, ClientChallenge)
+    assert HexToByte("aa aa aa aa aa aa aa aa 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00") == LMv1Response  # [MS-NLMP] page 75
+    assert HexToByte("75 37 f8 03 ae 36 71 28 ca 45 82 04 bd e7 ca f8 1e 97 ed 26 83 26 72 32") == NTLMv1Response
+
+    assert HexToByte("0c 86 8a 40 3b fd 7a 93 a3 00 1e f2 2e f0 2e 3f") == create_NT_hashed_password_v2(Password, User, Domain)    # [MS-NLMP] page 76
+    ResponseKeyLM = ResponseKeyNT = create_NT_hashed_password_v2(Password, User, Domain)
+    (NTLMv2Response,LMv2Response) = ComputeResponse(ResponseKeyNT, ResponseKeyLM, ServerChallenge, ServerName, ClientChallenge, Time)
+    assert HexToByte("86 c3 50 97 ac 9c ec 10 25 54 76 4a 57 cc cc 19 aa aa aa aa aa aa aa aa") == LMv2Response  # [MS-NLMP] page 76
+
+    # expected failure
+    # According to the spec in section '3.3.2 NTLM v2 Authentication' the NTLMv2Response should be longer than the value given on page 77 (this suggests a mistake in the spec)
+    #~ assert HexToByte("68 cd 0a b8 51 e5 1c 96 aa bc 92 7b eb ef 6a 1c") == NTLMv2Response, "\nExpected: 68 cd 0a b8 51 e5 1c 96 aa bc 92 7b eb ef 6a 1c\nActual:   %s" % ByteToHex(NTLMv2Response) # [MS-NLMP] page 77
diff --git a/virt-who.8 b/virt-who.8
index f26175e..4b2c283 100644
--- a/virt-who.8
+++ b/virt-who.8
@@ -2,7 +2,7 @@
 .SH NAME
 virt-who - Agent for reporting virtual guest IDs to Subscription Asset Manager.
 .SH SYNOPSIS
-virt-who [-d] [-i INTERVAL] [-b] [-o] [--libvirt|--vdsm|--esx|--rhevm]
+virt-who [-d] [-i INTERVAL] [-b] [-o] [--libvirt|--vdsm|--esx|--rhevm|--hyperv]
 .SH OPTIONS
 .TP
 \fB\-h\fR, \fB\-\-help\fR
@@ -31,6 +31,9 @@ Register ESX machines using vCenter
 .TP
 \fB\-\-rhevm\fR
 Register guests using RHEV\-M
+.TP
+\fB\-\-hyperv\fR
+Register guests using Hyper\-V
 .IP
 .SS vCenter/ESX options
 .IP
@@ -69,6 +72,25 @@ Username for connecting to RHEV\-M
 .TP
 \fB\-\-rhevm\-password\fR=\fIPASSWORD\fR
 Password for connecting to RHEV\-M
+.IP
+.SS Hyper\-V options
+.IP
+Use this options with \fB\-\-hyperv\fR
+.TP
+\fB\-\-hyperv\-owner\fR=\fIOWNER\fR
+Organization who has purchased subscriptions of the products
+.TP
+\fB\-\-hyperv\-env\fR=\fIENV\fR
+Environment where the Hyper\-V belongs to
+.TP
+\fB\-\-hyperv\-server\fR=\fISERVER\fR
+URL of the Hyper\-V server to connect to
+.TP
+\fB\-\-hyperv\-username\fR=\fIUSERNAME\fR
+Username for connecting to Hyper\-V
+.TP
+\fB\-\-hyperv\-password\fR=\fIPASSWORD\fR
+Password for connecting to Hyper\-V
 .PP
 .SH ENVIRONMENT
 virt-who also reads environmental variables. They have the same name as command line arguments but upper-cased, with underscore instead of dash and prefixed with VIRTWHO_ (e.g. VIRTWHO_ONE_SHOT). Empty variables are considered as disabled, non-empty as enabled
@@ -127,6 +149,13 @@ Use ESX (vCenter) as virtualization backend and specify option required to conne
 
 Use RHEV-M as virtualization backend and specify option required to connect to RHEV-M server.
 
+.TP
+4. Hyper-V
+
+# virt-who --hyperv --hyperv-owner=HYPERV_OWNER  --hyperv-env=HYPERV_ENV --hyperv-server=HYPERV_SERVER --hyperv-username=HYPERV_USERNAME --hyperv-password=HYPERV_PASSWORD
+
+Use Hyper-V as virtualization backend and specify option required to connect to Hyper-V server.
+
 .SH LOGGING
 virt-who always writes error output to file /var/log/rhsm/rhsm.log. In all modes, excluding background ("-b"), it writes same output also to the standard error output.
 
diff --git a/virt-who.conf b/virt-who.conf
index 043b695..2c117d2 100644
--- a/virt-who.conf
+++ b/virt-who.conf
@@ -18,7 +18,7 @@ VIRTWHO_DEBUG=0
 # configuration.
 #VIRTWHO_INTERVAL=0
 
-# virt-who mode, enable only one option from following 4:
+# virt-who mode, enable only one option from following 5:
 # Use libvirt to list virtual guests [default]
 #VIRTWHO_LIBVIRT=1
 # Use vdsm to list virtual guests
@@ -27,6 +27,8 @@ VIRTWHO_DEBUG=0
 #VIRTWHO_ESX=0
 # Register guests using RHEV-M
 #VIRTWHO_RHEVM=0
+# Register guest using Hyper-V
+#VIRTWHO_HYPERV=0
 
 # Option for ESX mode
 #VIRTWHO_ESX_OWNER=
@@ -41,3 +43,10 @@ VIRTWHO_DEBUG=0
 #VIRTWHO_RHEVM_SERVER=
 #VIRTWHO_RHEVM_USERNAME=
 #VIRTWHO_RHEVM_PASSWORD=
+
+# Options for HYPER-V mode
+#VIRTWHO_HYPERV_OWNER=
+#VIRTWHO_HYPERV_ENV=
+#VIRTWHO_HYPERV_SERVER=
+#VIRTWHO_HYPERV_USERNAME=
+#VIRTWHO_HYPERV_PASSWORD=
diff --git a/virt-who.py b/virt-who.py
index 7b15f14..85d998a 100644
--- a/virt-who.py
+++ b/virt-who.py
@@ -28,6 +28,7 @@ from virt import Virt, VirtError
 from vdsm import VDSM
 from vsphere import VSphere
 from rhevm import RHEVM
+from hyperv import HyperV
 from event import virEventLoopPureStart
 from subscriptionmanager import SubscriptionManager, SubscriptionManagerError
 
@@ -98,6 +99,8 @@ class VirtWho(object):
             self.tryRegisterEventCallback()
         elif self.options.virtType == "rhevm":
             self.virt = RHEVM(self.logger, self.options.server, self.options.username, self.options.password)
+        elif self.options.virtType == "hyperv":
+            self.virt = HyperV(self.logger, self.options.server, self.options.username, self.options.password)
         else:
             # ESX
             self.virt = VSphere(self.logger, self.options.server, self.options.username, self.options.password)
@@ -171,7 +174,7 @@ class VirtWho(object):
                 return False
 
         try:
-            if self.options.virtType not in ["esx", "rhevm"]:
+            if self.options.virtType not in ["esx", "rhevm", "hyperv"]:
                 virtualGuests = self.virt.listDomains()
             else:
                 virtualGuests = self.virt.getHostGuestMapping()
@@ -187,7 +190,7 @@ class VirtWho(object):
                 return False
 
         try:
-            if self.options.virtType not in ["esx", "rhevm"]:
+            if self.options.virtType not in ["esx", "rhevm", "hyperv"]:
                 self.subscriptionManager.sendVirtGuests(virtualGuests)
             else:
                 result = self.subscriptionManager.hypervisorCheckIn(self.options.owner, self.options.env, virtualGuests)
@@ -325,6 +328,7 @@ def main():
     parser.add_option("--vdsm", action="store_const", dest="virtType", const="vdsm", help="Use vdsm to list virtual guests")
     parser.add_option("--esx", action="store_const", dest="virtType", const="esx", help="Register ESX machines using vCenter")
     parser.add_option("--rhevm", action="store_const", dest="virtType", const="rhevm", help="Register guests using RHEV-M")
+    parser.add_option("--hyperv", action="store_const", dest="virtType", const="hyperv", help="Register guests using Hyper-V")
 
     esxGroup = OptionGroup(parser, "vCenter/ESX options", "Use this options with --esx")
     esxGroup.add_option("--esx-owner", action="store", dest="owner", default="", help="Organization who has purchased subscriptions of the products")
@@ -342,6 +346,14 @@ def main():
     rhevmGroup.add_option("--rhevm-password", action="store", dest="password", default="", help="Password for connecting to RHEV-M")
     parser.add_option_group(rhevmGroup)
 
+    hypervGroup = OptionGroup(parser, "Hyper-V options", "Use this options with --hyperv")
+    hypervGroup.add_option("--hyperv-owner", action="store", dest="owner", default="", help="Organization who has purchased subscriptions of the products")
+    hypervGroup.add_option("--hyperv-env", action="store", dest="env", default="", help="Environment where the Hyper-V belongs to")
+    hypervGroup.add_option("--hyperv-server", action="store", dest="server", default="", help="URL of the Hyper-V server to connect to")
+    hypervGroup.add_option("--hyperv-username", action="store", dest="username", default="", help="Username for connecting to Hyper-V")
+    hypervGroup.add_option("--hyperv-password", action="store", dest="password", default="", help="Password for connecting to Hyper-V")
+    parser.add_option_group(hypervGroup)
+
     (options, args) = parser.parse_args()
 
     # Handle enviromental variables
@@ -379,6 +391,11 @@ def main():
     if env in ["1", "true"]:
         options.virtType = "rhevm"
 
+    env = os.getenv("VIRTWHO_HYPERV", "0").strip().lower()
+    if env in ["1", "true"]:
+        options.virtType = "hyperv"
+
+
     def checkEnv(variable, option, name):
         """
         If `option` is empty, check enviromental `variable` and return its value.
@@ -407,6 +424,14 @@ def main():
         if len(options.password) == 0:
             options.password = os.getenv("VIRTWHO_RHEVM_PASSWORD", "")
 
+    if options.virtType == "hyperv":
+        options.owner = checkEnv("VIRTWHO_HYPERV_OWNER", options.owner, "owner")
+        options.env = checkEnv("VIRTWHO_HYPERV_ENV", options.env, "env")
+        options.server = checkEnv("VIRTWHO_HYPERV_SERVER", options.server, "server")
+        options.username = checkEnv("VIRTWHO_HYPERV_USERNAME", options.username, "username")
+        if len(options.password) == 0:
+            options.password = os.getenv("VIRTWHO_HYPERV_PASSWORD", "")
+
     if options.interval < 0:
         logger.warning("Interval is not positive number, ignoring")
         options.interval = 0
@@ -434,7 +459,7 @@ def main():
             logger.debug("Starting event loop")
             virEventLoopPureStart()
         else:
-            logger.warning("Listening for events is not available in VDSM, ESX or RHEV-M mode")
+            logger.warning("Listening for events is not available in VDSM, ESX, RHEV-M or Hyper-V mode")
 
     global RetryInterval
     if options.interval < RetryInterval: