Microsoft teams bot - could not parse tenant id - c#

I'm working on a bot for MS Teams, and running into an issue. When trying to initiate a conversation from the bot, I get this error:
Microsoft.Rest.HttpOperationException: Could not parse tenant id
I haven't been able to find anywhere in the docs that mentions a required Tenant ID, and I never set one up in the application. How can I specify this, or is the root cause something else?
Below is the code I am using that returns the error (strings obfuscated).
private ConversationResourceResponse GetConversation(IActivity activity)
{
var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
var userAccount = new ChannelAccount("user#domain.com");
var botAccount = new ChannelAccount("#botHandle", "botName");
var conversationId = connector.Conversations.CreateDirectConversation(botAccount, userAccount);
return conversationId;
}
Thank you!

There is a special behaviour in MS Teams when you want to create a conversation, so you have to use a specific method provided by MS Teams NuGet package:
// Create or get existing chat conversation with user
var response = client.Conversations.CreateOrGetDirectConversation(activity.Recipient, activity.From, activity.GetTenantId());
You can see that the method has the tenantId in parameter.
The NuGet package is called Microsoft.Bot.Connector.Teams and is available here.
More details on the MS Teams documentation (it's not detailed on Bot framework side):
https://learn.microsoft.com/en-us/microsoftteams/platform/scenarios/bots-personal-conversations#starting-a-11-conversation

Related

How do I send a notification to a user in Teams via the Bot Framework?

I have a Bot created with v4 of the Microsoft Bot Framework. I can successfully use this bot in the "Test in Web Chat" portion in the Azure Portal. I can also successfully use this bot in an app that I've created in Microsoft Teams. I now want to send a notification from the "Test in Web Chat" piece to a specific user in Teams. For example, in the "Test in Web Chat" piece, I'd like to enter
Hello someuser#mytenant.com
When this is sent via the "Test in Web Chat" piece, I'd like to show "Hello" in Microsoft Teams to only someuser#mytenant.com. I have successfully tokenized the string from the "Test in Web Chat". Thus, I know what I want to send, and who I want to send it to. However, I do not know how to actually send it.
Currently, I have the following in my bot:
public class EchoBot : ActivityHandler
{
private ConcurrentDictionary<string, ConversationReference> _conversationReferences;
public EchoBot(ConcurrentDictionary<string, ConversationReference> conversationReferences)
{
_conversationReferences = conversationReferencs;
}
private void AddConversationReference(Activity activity)
{
var reference = activity.GetConversationReference();
_conversationReferences.AddOrUpdate(reference.User.Id, reference, (key, newValue) => reference);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> context, CancellationToken cancellationToken)
{
AddConversationReference(context.Activity as Activity);
var parameters = GetParameters(); // Parses context.Activity.Text;
// Send a message to the target (i.e. someuser#mytenant.com)
var connection = new Microsoft.Bot.Connector.ConnectorClient(new Uri(context.Activity.ServiceUrl));
var tenant = context.Activity.GetChannelData<TeamsChannelData>().Tenant;
// how do I send the message to parameters.Target?
// Confirm message was sent to the sender
var confirmation = $"Message was sent to {parameters.Target}.";
await context.SendActivityAsync(MessageFactory.Text(confirmation));
}
}
I've reviewed how to send proactive notifications to users. However, I've been unsuccessful in a) getting the user specified in parameters.Target and b) sending a notification to that user. What am I missing?
First, you'll need to map user#email.com to their Teams userId (maybe with a static dictionary), which is in the format of:
29:1I9Is_Sx0O-Iy2rQ7Xz1lcaPKlO9eqmBRTBuW6XzXXXXXXXXMij8BVMdBcL9L_RwWNJyAHFQb0TXXXXXX
You can get the Teams UserId by either:
Querying the roster, or
Having the user message the bot, and setting a breakpoint on an incoming message, looking at the Activity.ChannelData for the Teams userId, or
Dynamically build a static dictionary of all incoming messages that stores the user's email mapped to their Teams userId (I believe both are found in Activity.ChannelData).
Note: #1 and #2 both require a user to message the bot, first, which sort of defeats the purpose of proactive messages
After you have the appropriate Teams IDs, you just send a proactive message to a Teams user. The end of this link also mentions trustServiceUrl, which you may find handy if you run into permissions/auth issues when trying to send a proactive message.

How to migrate 1:1 proactive messages functionality in Teams with Bot Framework V4 C# SDK

I have a Bot Framework V3 SDK implementation (working fine) that can initiate a 1:1 private chat with any user in a specific Team using a Microsoft Teams Bot (installed on the same team).
I am having problems trying to migrate it to the V4 SDK.
I read on various articles and other questions that it's not possible to do it unless the user contacts the the bot first (to avoid spamming to users), but this is not true with the V3 version and not an option for the functionality that I need.
The original solution uses the "CreateOrGetDirectConversation" extension method from the Microsoft.Bot.Connector.Teams assembly, but it's not available in the V4 version of the assembly.
I tried using the CreateDirectConversation/CreateDirectConversationAsync methods but with no success (they always result in "Bad Request" errors).
This is the code actually working using the V3 libraries:
// Create 1:1 conversation
var conversation = connectorClient.Conversations.CreateOrGetDirectConversation(botAccount, user, tenantId);
// Send message to the user
var message = Activity.CreateMessageActivity();
message.Type = ActivityTypes.Message;
message.Text = "My message";
connectorClient.Conversations.SendToConversation((Activity)message, conversation.Id);
And I am finding it very hard to migrate. Am I missing something?
Per the docs, Proactive messaging for bots:
Bots can create new conversations with an individual Microsoft Teams user as long as your bot has user information obtained through previous addition in a personal, groupChat or team scope. This information enables your bot to proactively notify them. For instance, if your bot was added to a team, it could query the team roster and send users individual messages in personal chats, or a user could #mention another user to trigger the bot to send that user a direct message.
Note: The Microsoft.Bot.Builder.Teams extension is still in Prerelease for V4, which is why samples and code are kind of hard to find for it.
Adding the Middleware
In Startup.cs:
var credentials = new SimpleCredentialProvider(Configuration["MicrosoftAppId"], Configuration["MicrosoftAppPassword"]);
services.AddSingleton(credentials);
[...]
services.AddBot<YourBot>(options =>
{
options.CredentialProvider = credentials;
options.Middleware.Add(
new TeamsMiddleware(
new ConfigurationCredentialProvider(this.Configuration)));
[...]
Prepping Your Bot
In your main <YourBot>.cs:
private readonly SimpleCredentialProvider _credentialProvider;
[...]
public <YourBot>(ConversationState conversationState, SimpleCredentialProvider CredentialProvider)
{
_credentialProvider = CredentialProvider;
[...]
Sending the Message
var teamConversationData = turnContext.Activity.GetChannelData<TeamsChannelData>();
var connectorClient = new ConnectorClient(new Uri(activity.ServiceUrl), _credentialProvider.AppId, _credentialProvider.Password);
var userId = <UserIdToSendTo>;
var tenantId = teamConversationData.Tenant.Id;
var parameters = new ConversationParameters
{
Members = new[] { new ChannelAccount(userId) },
ChannelData = new TeamsChannelData
{
Tenant = new TenantInfo(tenantId),
},
};
var conversationResource = await connectorClient.Conversations.CreateConversationAsync(parameters);
var message = Activity.CreateMessageActivity();
message.Text = "This is a proactive message.";
await connectorClient.Conversations.SendToConversationAsync(conversationResource.Id, (Activity)message);
Note: If you need to get user ID's, you can use:
var members = (await turnContext.TurnState.Get<IConnectorClient>().Conversations.GetConversationMembersAsync(
turnContext.Activity.GetChannelData<TeamsChannelData>().Team.Id).ConfigureAwait(false)).ToList();
Also, I didn't need this in my testing, but if you get 401 errors, you may need to trust the Teams ServiceUrl:
MicrosoftAppCredentials.TrustServiceUrl(turnContext.Activity.ServiceUrl);
Resources
Teams Extension on MyGet
Teams Extension MyGet Package Repo
Samples using the extension
Proactive Teams Sample
Useful, unofficial blog post

Teams bot Activity.CreateReply throwing NullReferenceException

I'm working on a bot for Microsoft Teams. I am using the custom bot feature. I got the bot working as a sideloaded package, but due to the constraints of my network, I need to keep the bot internal and use the custom bot feature. I am currently testing it by using ngrok to tunnel to my localhost.
I am now running into an issue when I try to create my reply. Whenever I call this:
var reply = activity.CreateReply(message.ReadToEnd());
I get a NullReferenceException saying that the "Object reference not set to an instance of an object". message is an open .txt file. I get this error every time I call activity.CreateReply(). The part that I don't understand is that everything works as intended in the Bot Framework Emulator and when the bot is a sideloaded package, but not when the bot is a custom bot.
Here's my full Post method:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
if (activity.Type == ActivityTypes.Message)
{
// Commands:
// Retrieve TFS Work Item(s)
if (new Regex(#"\but\s?\d{5}\b").IsMatch(activity.Text.ToLower()))
{
var reply = new RetrieveWorkItem();
await connector.Conversations.ReplyToActivityAsync(reply.Response(activity));
}
// Help
else if (activity.Text.ToLower().Contains("help"))
{
var message = File.OpenText($"{System.AppDomain.CurrentDomain.BaseDirectory}/Messages/HelpMessage.txt");
var reply = activity.CreateReply(message.ReadToEnd());
await connector.Conversations.ReplyToActivityAsync(reply);
}
// Not Recognized
else
{
var reply = activity.CreateReply("Command not recognized. Type \"#Keller Bot Help\" for a list of commands.");
await connector.Conversations.ReplyToActivityAsync(reply);
}
}
else
{
HandleSystemMessage(activity, connector);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
Here's the full error and StackTrace sent by the bot: https://pastebin.com/ZSrjrA9z
You say you're implementing this as a custom bot, per the instructions here. The issue is that it appears as if you're using the Bot Framework messaging calls (e.g. CreateReply()), which won't work since you're not dealing with a registered BF bot when you go through the custom bot process.
Instead, you can just create a new Activity() and return that in response to the HttpPost request.
We do have a sample you can check out, in case that helps.

How to get all available groups of Microsoft bot Skype

I'm trying to get away from the use of desktop Skype API with wrapper skype4com. I look in the direction of Microsoft Bot Framework. I created and registered my Skype bot with the Microsoft Bot Connector like here: Getting started with the Connector
Also I added my bot into several group conversations. Now I have a task to write a message to a specific group conversation. For this I need to get a list of group conversations in which the bot is. I would like to get information about all group conversations my Skype bot such as conversation id and conversation name. I have been unable to find any information about it. Anyone know how to get a list of all groups conversations for Microsoft Bot?
When your bot is added to a conversation, it receives a message of type conversationUpdate (see this page). You will have to maintain the list of conversations yourself, having some kind of a storage, following the changes signalled by this message type.
Example:
[BotAuthentication]
public class MessagesController : ApiController
{
private List<string> m_conversationIds;
public MessagesController()
{
m_conversationIds = new List<string>();
}
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type != ActivityTypes.Message)
{
return await HandleSystemMessage(activity);
}
// ...
}
private async Task<HttpResponseMessage> HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.ConversationUpdate)
{
m_conversationIds.Add(message.Conversation.Id);
return Request.CreateResponse(HttpStatusCode.OK);
}
// ...
}
}
Of course, you might need additional pieces of information, not just the conversation ID. Also, you'll need to filter so that you only store the data when the update is about adding your bot to a conversation (as opposed to e.g. removing it).
NOTE: The example is just to get you started. An in-memory store like the list above is neither scalable nor robust.
Right now this is not enumerable within the Bot Framework SDK (or the protocol). The system assumes the bot caches information about the messages/groups its in when the bot is added to a given conversation.
Get group info on launch

Azure Active Directory Graph Client 2.0 - Context is not currently tracking the entity

I have recently installed the Azure Active Directory Graph Client Library 2.0.2 Nuget package and am unable to add Members to Groups both adding a group to a group or adding a user to a group I am getting the following error when the AddLink function is called:
“[System.InvalidOperationException] = {"The context is not currently tracking the entity."}
My code:
IGroup group = azureClient.Groups.GetByObjectId("Guid here").ExecuteAsync().Result;
IGroup groupToAdd = azureClient.Groups.GetByObjectId("Guid here").ExecuteAsync().Result;
azureClient.Context.AddLink(group, "Members", groupToAdd);
azureClient.Context.SaveChanges();
I have been unable to find any mention of this error in relation to the Azure Active Directory Graph Client Library from doing Google searches so any help on this would be much appreciated.
We’ve just released an update to the Graph client library, that fixes this problem.
You should now be able to add members to groups. The mechanism is a little different from using AddLinks (and hopefully simpler).
We also have a new blog describing the client library which talks about this and many other things: http://blogs.msdn.com/b/aadgraphteam/archive/2014/12/12/announcing-azure-ad-graph-api-client-library-2-0.aspx
For reference, to add a member to a group:
{groupObject}.Members.Add({entityObject} as DirectoryObject);
So for example, to update a group with a new user member:
myGroup.Members.Add(userToBeAdded as DirectoryObject);
await myGroup.UpdateAsync();
NOTE: The same construct can be used to add users to a DirectoryRole object. Groups and users may be added to a group, however, for now, only users can be added to a DirectoryRole.
Hope this helps,
I had the same issue, and the documentation wasn't very clear, so maybe this will help others. You cannot add users as members of an IGroup, but only to a Group. You also cannot add IDirectoryObjects to the Members collection, but only DirectoryObjects. You must first cast your IUser and IGroup objects. The following code is what I have working at the moment:
var graphClient = new ActiveDirectoryClient(new Uri(ConfigHelper.GraphServiceRoot), async () => await GetUserTokenAsync(cache));
var actualUser = await graphClient.Users.GetByObjectId(matchedUser.ObjectId).ExecuteAsync();
var actualGroup = (Group) await graphClient.Groups.GetByObjectId(matchedGroup.ObjectId).ExecuteAsync();
actualGroup.Members.Add(actualUser as DirectoryObject);
await graphClient.Context.SaveChangesAsync();
I tried this new syntax but still does not work.
public async Task<Result<dynamic>> addUserToAzureGroup(Group AzGroup, User AzUser)
{
// link the found user with the found group
try
{
AzGroup.Members.Add(AzUser as DirectoryObject);
await AzGroup.UpdateAsync();
}
catch (Exception ex)
{
Exception myEx = new Exception(ex.Message);
retResult.Exception = myEx;
return retResult;
}
return retResult;
}
I have almost the same error text in the execption message: The context is already tracking the relationship
Any news on that issue? Could anyone guess why that happens?
I also tried from the manage.windowsAzure.com UI and still cannot add the user! I get this error: Could not add members to group 'myAzAD_group'.

Categories