none
Messaging to and From an IoT Hub Module RRS feed

  • Question

  • I understand that we currently cannot send messages from one IoT device to another

    https://social.msdn.microsoft.com/Forums/en-US/a09a3b80-fdb6-4ad3-b8f3-186be097dc9a/device-to-device-messaging?forum=azureiothub

    I also understand that it is impossible to send messages from an IoT device to a module running on IoT Edge

    https://social.msdn.microsoft.com/Forums/en-US/0c9ee626-304d-4e56-84f7-4fa91c9ba096/iotedge-quotcurrently-a-module-cannot-receive-cloudtodevice-messagesquot?forum=azureiothub

    However, seeing as we have an example of sending temperature data from a "device" (which I guess is actually a module) and then filtering it with another module, it seems it is possible to send and receive messages from one module to another. Is this correct?

    If so, what should the sending code look like? Currently the filtering example only shows how to forward messages, not create them.

    Friday, July 6, 2018 10:56 PM

All replies

  • It seems, yes, as long as I use the IoTHubModuleClient() class

    This is my api for sending and receiving

    import random
    import time
    import sys
    import json
    
    import iothub_client
    from iothub_client import IoTHubModuleClient, IoTHubClientError, IoTHubTransportProvider
    from iothub_client import IoTHubMessage, IoTHubMessageDispositionResult, IoTHubError
    
    PROTOCOL = IoTHubTransportProvider.MQTT
    MESSAGE_TIMEOUT = 10000
    TEMPERATURE_THRESHOLD = 25
    TWIN_CALLBACKS = 0
    RECEIVE_CALLBACKS = 0
    
    
    def receive_message_callback(message, hubManager):
        print('CALLING BACK')
        global RECEIVE_CALLBACKS
        global TEMPERATURE_THRESHOLD
        message_buffer = message.get_bytearray()
        size = len(message_buffer)
        message_text = message_buffer[:size].decode('utf-8')
        print ( "    Data: <<<%s>>> & Size=%d" % (message_text, size) )
        map_properties = message.properties()
        key_value_pair = map_properties.get_internals()
        print ( "    Properties: %s" % key_value_pair )
        RECEIVE_CALLBACKS += 1
        print ( "    Total calls received: %d" % RECEIVE_CALLBACKS )
        data = json.loads(message_text)
        print(data)
        # hubManager.forward_event_to_output("output1", message, 0)
        return IoTHubMessageDispositionResult.ACCEPTED
    
    
    def construct_message(message_body, topic):
        try:
            msg_txt_formatted = message_body
            message = IoTHubMessage(msg_txt_formatted)
    
            # Add a custom application property to the message.
            # An IoT hub can filter on these properties without access to the message body.
            prop_map = message.properties()
            prop_map.add("topic", topic)
    
            # TODO Use logging
            # Send the message.
            print( "Sending message: %s" % message.get_string() )
    
        except IoTHubError as iothub_error:
            print ( "Unexpected error %s from IoTHub" % iothub_error )
            return
    
        return message
    
    
    class HubManager(object):
    
        def __init__(
                self,
                protocol=IoTHubTransportProvider.MQTT):
            self.client_protocol = protocol
            self.client = IoTHubModuleClient()
            self.client.create_from_environment(protocol)
    
            # set the time until a message times out
            self.client.set_option("messageTimeout", MESSAGE_TIMEOUT)
    
            # sets the callback when a message arrives on "input1" queue.  Messages sent to
            # other inputs or to the default will be silently discarded.
            self.client.set_message_callback("input1", receive_message_callback, self)
    
        # # Forwards the message received onto the next stage in the process.
        # def forward_event_to_output(self, outputQueueName, event, send_context):
        #     self.client.send_event_async(
        #         outputQueueName, event, send_confirmation_callback, send_context)
    
        def send_message(self, message):
            # No callback
            # TODO what is the third arg?
            self.client.send_event_async(message, (lambda x, y, z: None), None)
    
        def publish(self, topic, msg):
            message = construct_message(msg, topic)
            self.send_message(message)
            print('publishing %s', msg)
    
    
    def initialize():
        hub_manager = HubManager(PROTOCOL)
    
        return hub_manager
    
    

    Friday, July 6, 2018 11:15 PM
  • Hmm, except in

    iotedge logs edgeHub 

    I'm getting a lot of

    [ERR] - Received message does not contain a device Id

    errors.

    What's up with that?

    And if I try to view the messages using the procedure described here

    https://docs.microsoft.com/en-us/azure/iot-edge/tutorial-python-module#view-generated-data

    (sorry I can't embed links because my account has not been "verified" yet).

    I don't see any of the messages.

    Friday, July 6, 2018 11:24 PM
  • Hello Jak_Quixote,

    In Azure IoT Edge, messages passed based on declarative routes. Module can set input and output to receive message from and send message to.

    For example this route:

            "routes": {
              "sensorToFilter": "FROM /messages/modules/tempSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/filtermodule/inputs/input1\")",
              "filterToIoTHub": "FROM /messages/modules/filtermodule/outputs/output1 INTO $upstream"
            },


    Here tempSensor module specified the output "tempertureOutput" like this:

    await deviceClient.SendEventAsync("temperatureOutput", message);

    And filtermodule specified the input "input1" and "output1" like this:

    // Register callback to be called when a message is received by the module
    await ioTHubModuleClient.SetInputMessageHandlerAsync("input1", FilterMessages, ioTHubModuleClient);
    await moduleClient.SendEventAsync("output1", filteredMessage);
    

    And $upstream for sending messages to IoT Hub.

    Reference:

    Create an IoT Edge module project

    Using the IoT Edge hub

    Declare routes

    Best regards,

    Rita


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Monday, July 9, 2018 9:22 AM
  • Hi Rita,

    Thanks for your reply.

    Perhaps I should have mentioned more explicitly that I am using the python SDK. I am aware of the routing and the filtering example. I think I need help with the initial message construction/sending. I have my routes set up like this.

    {
    "routes": {
    "AllToShepherd": "FROM /* INTO BrokeredEndpoint(\"/modules/Shepherd/inputs/input1\")",
    "AllToCloud": "FROM /* INTO $upstream"
    }

    }

    Like I said above, I am getting an error and not seeing any messages when I attempt to monitor device to cloud messages. Can you provide some guidance on constructing and sending messages from the Python module (not device) SDK?


    • Edited by Jak_Quixote Tuesday, July 10, 2018 12:00 AM clarify EDGE MODULE sdk is different from device sdk
    Monday, July 9, 2018 3:57 PM
  • Hello Jak_Quixote,

    I know you use Python and just use C# sample to describe route because route is the same for Python and C# or other languages.

    Still I have some question about your route and can do reproduce your issue.

    1. How many modules do you have? I can only see one module named "Shepherd".
    2. If there is only one Shepherd module, while all message from this modules then go in itself via "input1", why?
    3. If there is other module, why message not routed to it or from it.

    From your first route "AllToShepherd", all messages to module Shepherd then not output to any sink.

    From second route "AllToCloud", all messages from module directly routed to IoT Hub and there is no any module to module communication happened.

    Best regards,

    Rita


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.



    Tuesday, July 10, 2018 9:17 AM
  • The flow should be from a module named SampleModule to Shepherd and then to the IoTHub Cloud. Shouldn't the first rule capture ALL messages and send them to Shepherd? And the second rule also send all messages to the cloud?

    Tuesday, July 10, 2018 3:42 PM
  • Tried it again with these rules

    {
      "routes": {
        "AllToShepherd": "FROM /messages/modules/SampleModule/outputs/output1 INTO BrokeredEndpoint(\"/modules/Shepherd/inputs/input1\")",
        "AllToCloud": "FROM /* INTO $upstream"
      }
    }

    Still no messages making it to the cloud. Same error message on edgeHub

    Tuesday, July 10, 2018 7:47 PM
  • Hi Jak_Quixote,

    The route you provided above means the messages flow like below instead of you expected that SampleModule=>Shepherd =>IoTHub.

    1. SampleModule=>Shepherd module

    2. All output =>Azure IoT Hub

    However the route should works for the Azure IoT Hub to receive the messages from Edge device if all the settings well config. To narrow down whether the issue was relative to the connection problem or the coding issue, I suggest that you deploy the sample module tempSensor from this link.

    Regards & Fei


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Friday, July 13, 2018 6:44 AM
  • Tried deploying the tempSensor, now I get those messages, but still none of mine
    Monday, July 16, 2018 4:46 PM
  • Hi Jax_Quixote,

    I am not familiar with Python, however based on the code provided in the second post. It seems that you just define the method to publish the message and didn't call it. Please feel free to let me know if I misunderstood.

    And dose the module deployment and running successfully? You can check the status of modules using the command iotedge list

    Regards & Fei


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Wednesday, July 18, 2018 12:01 PM
  • Yes, I did check that everything is running ok with the list command

    That is just my module with my Azure functions/methods. I didn't copy/paste my main() etc which is in a different module but they are being called.

    So in conclusion, no one knows what the error about the message not having a device ID means

    Wednesday, July 18, 2018 3:50 PM
  • Hi Jax_Quixote,

    >So in conclusion, no one knows what the error about the message not having a device ID means

    Not sure whether this issue is relative to receiving the messages from Azure IoT Hub.

    I also try to create a simple code sample to send messages from edge module and it works well for me. And here is the tutorial I followed. We need to change the module template using Python Module since it uses C# like figure below:

    And here is the whole sample code you can refer:

    # Copyright (c) Microsoft. All rights reserved.
    # Licensed under the MIT license. See LICENSE file in the project root for
    # full license information.
    
    import random
    import time
    import sys
    import iothub_client
    from iothub_client import IoTHubModuleClient, IoTHubClientError, IoTHubTransportProvider
    from iothub_client import IoTHubMessage, IoTHubMessageDispositionResult, IoTHubError
    
    # messageTimeout - the maximum time in milliseconds until a message times out.
    # The timeout period starts at IoTHubModuleClient.send_event_async.
    # By default, messages do not expire.
    MESSAGE_TIMEOUT = 10000
    
    # global counters
    RECEIVE_CALLBACKS = 0
    SEND_CALLBACKS = 0
    
    # Choose HTTP, AMQP or MQTT as transport protocol.  Currently only MQTT is supported.
    PROTOCOL = IoTHubTransportProvider.MQTT
    
    # Callback received when the message that we're forwarding is processed.
    def send_confirmation_callback(message, result, user_context):
        global SEND_CALLBACKS
        print ( "Confirmation[%d] received for message with result = %s" % (user_context, result) )
        map_properties = message.properties()
        key_value_pair = map_properties.get_internals()
        print ( "    Properties: %s" % key_value_pair )
        SEND_CALLBACKS += 1
        print ( "    Total calls confirmed: %d" % SEND_CALLBACKS )
    
    
    # receive_message_callback is invoked when an incoming message arrives on the specified 
    # input queue (in the case of this sample, "input1").  Because this is a filter module, 
    # we will forward this message onto the "output1" queue.
    def receive_message_callback(message, hubManager):
        global RECEIVE_CALLBACKS
        message_buffer = message.get_bytearray()
        size = len(message_buffer)
        print ( "    Data: <<<%s>>> & Size=%d" % (message_buffer[:size].decode('utf-8'), size) )
        map_properties = message.properties()
        key_value_pair = map_properties.get_internals()
        print ( "    Properties: %s" % key_value_pair )
        RECEIVE_CALLBACKS += 1
        print ( "    Total calls received: %d" % RECEIVE_CALLBACKS )
        hubManager.forward_event_to_output("output1", message, 0)
        return IoTHubMessageDispositionResult.ACCEPTED
    
    
    class HubManager(object):
    
        def __init__(
                self,
                protocol=IoTHubTransportProvider.MQTT):
            self.client_protocol = protocol
            self.client = IoTHubModuleClient()
            self.client.create_from_environment(protocol)
    
            # set the time until a message times out
            self.client.set_option("messageTimeout", MESSAGE_TIMEOUT)
            
            # sets the callback when a message arrives on "input1" queue.  Messages sent to 
            # other inputs or to the default will be silently discarded.
            self.client.set_message_callback("input1", receive_message_callback, self)
    
        # Forwards the message received onto the next stage in the process.
        def forward_event_to_output(self, outputQueueName, event, send_context):
            self.client.send_event_async(
                outputQueueName, event, send_confirmation_callback, send_context)
                
        def SendSimulationData(self, msg):
            print"sending message..."
            message=IoTHubMessage(msg)
            self.client.send_event_async(
                "output1", message, send_confirmation_callback, 0)
            print"finished sending message..."
        
    
    
    def main(protocol):
        try:
            print ( "\nPython %s\n" % sys.version )
            print ( "IoT Hub Client for Python" )
    
            hub_manager = HubManager(protocol)
    
            print ( "Starting the IoT Hub Python sample using protocol %s..." % hub_manager.client_protocol )
            print ( "The sample is now waiting for messages and will indefinitely.  Press Ctrl-C to exit. ")
    
            while True:
                hub_manager.SendSimulationData("test msg")
                time.sleep(1)
    
        except IoTHubError as iothub_error:
            print ( "Unexpected error %s from IoTHub" % iothub_error )
            return
        except KeyboardInterrupt:
            print ( "IoTHubModuleClient sample stopped" )
    
    if __name__ == '__main__':
        main(PROTOCOL)
    
        

    And after you deploying the module, you should be able to see the messages by monitoring D2C messages via Visual Studio Code or Device Explorer.

    And please feel free to let us know if you still have the problem. And you may share a whole code sample so that other community could provide helpful suggestion for this issue.

    Hope it is helpful.

    Regards & Fei


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.


    Thursday, July 19, 2018 3:30 AM
  • Yes, I'm aware of that example code and the tutorial. You'll notice my code is essentially the same. However, it doesn't include message instantiation. It only forwards a message it already received. This is the one way in which my code is different.

    If you really think the issue is in how I am calling those functions then ok here is the complete code.

    # Copyright (c) Microsoft. All rights reserved.
    # Licensed under the MIT license. See LICENSE file in the project root for
    # full license information.
    
    import random
    import time
    import sys
    import iothub_client
    from iothub_client import IoTHubModuleClient, IoTHubClientError, IoTHubTransportProvider
    from iothub_client import IoTHubMessage, IoTHubMessageDispositionResult, IoTHubError
    
    # messageTimeout - the maximum time in milliseconds until a message times out.
    # The timeout period starts at IoTHubModuleClient.send_event_async.
    # By default, messages do not expire.
    MESSAGE_TIMEOUT = 10000
    
    # global counters
    RECEIVE_CALLBACKS = 0
    SEND_CALLBACKS = 0
    
    # Choose HTTP, AMQP or MQTT as transport protocol.  Currently only MQTT is supported.
    PROTOCOL = IoTHubTransportProvider.MQTT
    
    # Callback received when the message that we're forwarding is processed.
    def send_confirmation_callback(message, result, user_context):
        global SEND_CALLBACKS
        print ( "Confirmation[%d] received for message with result = %s" % (user_context, result) )
        map_properties = message.properties()
        key_value_pair = map_properties.get_internals()
        print ( "    Properties: %s" % key_value_pair )
        SEND_CALLBACKS += 1
        print ( "    Total calls confirmed: %d" % SEND_CALLBACKS )
    
    
    # receive_message_callback is invoked when an incoming message arrives on the specified 
    # input queue (in the case of this sample, "input1").  Because this is a filter module, 
    # we will forward this message onto the "output1" queue.
    def receive_message_callback(message, hubManager):
        global RECEIVE_CALLBACKS
        message_buffer = message.get_bytearray()
        size = len(message_buffer)
        print ( "    Data: <<<%s>>> & Size=%d" % (message_buffer[:size].decode('utf-8'), size) )
        map_properties = message.properties()
        key_value_pair = map_properties.get_internals()
        print ( "    Properties: %s" % key_value_pair )
        RECEIVE_CALLBACKS += 1
        print ( "    Total calls received: %d" % RECEIVE_CALLBACKS )
        hubManager.forward_event_to_output("output1", message, 0)
        return IoTHubMessageDispositionResult.ACCEPTED
    
    
    def construct_message(message_body, topic):
        try:
            msg_txt_formatted = message_body
            message = IoTHubMessage(msg_txt_formatted)
    
            # Add a custom application property to the message.
            # An IoT hub can filter on these properties without access to the message body.
            prop_map = message.properties()
            prop_map.add("topic", topic)
    
            # TODO Use logging
            # Send the message.
            print( "Sending message: %s" % message.get_string() )
    
        except IoTHubError as iothub_error:
            print ( "Unexpected error %s from IoTHub" % iothub_error )
            return
    
        return message
    
    
    class HubManager(object):
    
        def __init__(
                self,
                protocol=IoTHubTransportProvider.MQTT):
            self.client_protocol = protocol
            self.client = IoTHubModuleClient()
            self.client.create_from_environment(protocol)
    
            # set the time until a message times out
            self.client.set_option("messageTimeout", MESSAGE_TIMEOUT)
            
            # sets the callback when a message arrives on "input1" queue.  Messages sent to 
            # other inputs or to the default will be silently discarded.
            self.client.set_message_callback("input1", receive_message_callback, self)
    
        # Forwards the message received onto the next stage in the process.
        def forward_event_to_output(self, outputQueueName, event, send_context):
            self.client.send_event_async(
                outputQueueName, event, send_confirmation_callback, send_context)
    
        def send_message(self, message):
            # No callback
            # TODO what is the third arg?
            self.client.send_event_async(
                "output1", message, send_confirmation_callback, 0)
    
        def mypublish(self, topic, msg):
            message = construct_message(msg, topic)
            self.send_message(message)
            print('publishing %s', msg)
    
    def main(protocol):
        try:
            print ( "\nPython %s\n" % sys.version )
            print ( "IoT Hub Client for Python" )
    
            hub_manager = HubManager(protocol)
    
            print ( "Starting the IoT Hub Python sample using protocol %s..." % hub_manager.client_protocol )
            print ( "The sample is now waiting for messages and will indefinitely.  Press Ctrl-C to exit. ")
    
            while True:
                hub_manager.mypublish('testtopic', 'hello world this is a module')
                time.sleep(1)
    
        except IoTHubError as iothub_error:
            print ( "Unexpected error %s from IoTHub" % iothub_error )
            return
        except KeyboardInterrupt:
            print ( "IoTHubModuleClient sample stopped" )
    
    if __name__ == '__main__':
        main(PROTOCOL)


    I asked basically the same question here and it seems no one knows how to help

    https://social.msdn.microsoft.com/Forums/en-US/de3dd1ad-8ef7-413a-a851-2cacaa7c8b30/sending-from-edge-module-to-cloud?forum=azureiothub


    • Edited by Jak_Quixote Friday, July 20, 2018 3:38 PM Add msdn thread reference
    Friday, July 20, 2018 3:36 PM
  • Hi Jak_Quixote,

    The sample code in my preview post is exact showing how to send the message using SendSimulationData method  instead of  forwarding message. Please let me know whether it works for you.

    The reason why I asked for the sample code is trying to help identifying the root cause. And I will update here if there is any update.

    Regards & Fei


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.




    Monday, July 23, 2018 2:03 AM
  • Hi JaK_Quixote,

    I also tested the code sample you provided, it just works well on my side. I suggest that you double check the route defined in the project. To test the issue, I just add a simple route to the test module to upstream like below:

        "$edgeHub": {
          "properties.desired": {
            "schemaVersion": "1.0",
            "routes": {
              "sensorToSampleSensorModule": "FROM /messages/modules/tempSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/SampleSensorModule/inputs/input1\")",
              "SampleSensorModuleToIoTHub": "FROM /messages/modules/SampleSensorModule/outputs/output1 INTO $upstream",
              "SampleModule2ToIoTHub": "FROM /messages/modules/SampleModule2/outputs/* INTO $upstream"
            },
            "storeAndForwardConfiguration": {
              "timeToLiveSecs": 7200
            }
          }
        }

    And what's the logs of the custom module output(command: docker logs {YourModuleName})? 

    Here is the figure of logs output by the sample module and I monitor the messages using the Device Explorer:

    Please make sure that there is no any error in the logs. 

    Regards & Fei



    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Monday, July 23, 2018 3:41 AM
  • Hey, apologies for replying on this forum but it seems i can take good help from the samples you have mentioned as I trying to send data from wireless Activity sense detection sensor https://store.ncd.io/product/iot-wireless-activity-detection-sensor/ readings to table store and use the certain notification which will update the monitor the motion and send the message output to smart phone but as new start and have not good hands on these kind solution what easy I should use to make it possible ? I someone actually these kind of stuff, any valuable advice will be much helpful.    
    Monday, July 23, 2018 8:33 AM
  • Hi Ross65,

    Welcome to MSDN forum.

    This forum is used to discussing the issues about Azure IoT Hub. And this thread is discussing the issue about the edge module sending messages.

    Based on the description of the sensor link, the vendor already support Azure IoT. For how to use the IoT Long Range Wireless Activity Detection Sensor connecting Azure IoT, I suggest that you contact the vendor of this sensor directly to get the help more fast. 

    And if you have any problem about Azure IoT hub, please feel free to reopen a new thread in this forum. So that other communities could recognize the issue clearly and provide the helpful suggestion as soon as possible.

    Thanks for your understanding.

    Regards & Fei


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Monday, July 23, 2018 9:36 AM
  • Ah, my bad for not noticing that your code was different.

    There are errors as I mentioned before it complains about the messages not having a device id.

    Also why is it that when I go to msdn from my history I randomly get different language versions of the site? So far I've gotten English, Espanol, Francais, and Cesky

    Monday, July 23, 2018 4:34 PM
  • Hi Jak_Quixote,

    I saw this error when used the edge device as a translate gateway. Have you tried this scenario before? You can refer the same issue from #562

    I don't think the issue is caused by this error since you have mentioned the tempSensor works for you. So if the issue caused by the edgeHub, the tempSensor shouldn't work for you.  And the command mentioned in my preview post is checking the logs of your custom module, please check it to see whether there are any error to make sure that the code running successfully.

    Also to send the messages from edge device, here are the steps which works well for me for your reference:
    1. Follow tutorial below to install IoT edge run time on Windows with Linux containers

    Install Azure IoT Edge runtime on Windows to use with Linux containers

    2. Develop a python module by following tutorial below( change the module template using Python Module instead of C# Module)

    Use Visual Studio Code to develop and debug C# modules for Azure IoT Edge

    Please let me know whether it works for you by following the steps above in a clean environment.

    Regards & Fei


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Tuesday, July 24, 2018 2:37 AM