How can I read local MS Teams status
Asked Answered
Q

3

7

I am trying to implement a hardware busy light to show my Microsoft Teams presence so that my family to not enter the room I have the office while I am in a meeting. I am looking to implement something similar to:

The only problem I have with this setup is that I cannot get the MS Teams status.

The best way to go is by using MS Graph Presence API but my problem is that this is a company account and I don't have (and there is no way I could have) and app in the main subscription granted with the required scope: Presence.Read.

So I tried different ideas but none worked in the end:

  • check local running processes
  • check if MS Teams exposes any local API
  • check if there is a CLI available

This seems a simple idea, I mean, I see the status right there now while I am typing this message, I could as well do an app that gets a screenshot of the taskbar and extract the status from the icon, but is that really the only option I have?

Quindecennial answered 13/10, 2021 at 8:39 Comment(0)
P
8

I think I found something interesting for you.

Go to

C:\Users\user\AppData\Roaming\Microsoft\Teams 

you'll find a file called logs.txt

In this file you see if your current state changed

(current state: Available -> DoNotDisturb)

I would write a script with php or VB (depends on your skills) that read that logs.txt file like every minute and check for the last "current state" line.

Parolee answered 22/10, 2021 at 10:43 Comment(8)
Incredible, such a simple solution! Thank you, I will go this way, even if there is no guarantee this file will still be there in the future versions of MS Teams.Quindecennial
Just FYI, this just materialized: linkedin.com/posts/…Quindecennial
@Quindecennial Very nice!!Parolee
Do you know where the logs.txt is found on macOS?Mingmingche
^ found the log in ~Library/Application Support/Microsoft/Teams/* but did microsoft change their log behaviour for statuses?Mingmingche
so what I was afraid of happened: MS released a new Teams version where the logs file (although still present) does not contain the status information and is not flushed in realtime. Anyone knows how to get the status for the new Teams app?Quindecennial
@Quindecennial In Microsoft Teams, there has been a "Try the new Teams" option for a few weeks now. After activating it, you get a new version of Teams. In the new version, the logs.txt is not updated, or not updated immediately, whereas in the old version it is.Parolee
I just added an option to my app to use AzureAD and get the status using the MSGraph Presence API. Check the latest code here: github.com/miscalencu/OnlineStatusLightQuindecennial
S
2

I know this is an old thread, but I had this same issue recently and thought that posting a response with my solution may prove to be beneficial to someone else in the future.

I don't have access to MS Graph in my M365 Tenant, so I had to find alternative options to go about doing exactly what you are wanting to do. I spent an hour or two trying to figure out a graceful way to go about this same exact thing earlier this week and decided to use the UIAutomationClient Class.

You need to add references in your project to both UIAutomationClient and UIAutomationTypes.

These are the namespaces that need to be used:

using System.Threading;
using System.Threading.Tasks;
using System.Windows.Automation;

I start off with a button on a form that does the following:

        private void btnGetStatus_Click(object sender, EventArgs e)
        {
            // I am setting this to time out after 10 seconds, because sometimes it is buggy.
            using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) // 10-second timeout
            {
                // This is going to get my current status from MS Teams
                string presenceStatus = await GetTeamsPresence(cts.Token);

                // This sends the status to an ESP32 device that controls an LED Strip which changes based on my Teams Status.
                SendStatusToEsp32("987.654.321.000", presenceStatus);

                // This just updates a label on the form indicating the current status
                UpdateStatusText(presenceStatus);

                // This changes the Form icon and the Notification Tray icon to match what the Teams status is.
                SetFormIcon(presenceStatus);
            }
        }

public bool isChecking { get; private set; }
private AutomationElement storedTeamsWindow = null;

        private async Task<string> GetTeamsPresence(CancellationToken token)
        {
            isChecking = true;
            string presenceStatus = "Unknown";

            try
            {
                var rootElement = await Task.Run(() => AutomationElement.RootElement, token);

                // Check if we already have a valid storedTeamsWindow
                if (storedTeamsWindow != null)
                {
                    try
                    {
                        // Try to access a property to check if it's still valid
                        var cachedWindowName = storedTeamsWindow.Current.Name;
                    }
                    catch
                    {
                        // If accessing the property fails, the stored window is no longer valid
                        storedTeamsWindow = null;
                    }
                }

                if (storedTeamsWindow == null)
                {
                    AutomationElement teamsWindow = null;

                    // Find all windows
                    var windows = await Task.Run(() =>
                    {
                        var windowCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window);
                        return rootElement.FindAll(TreeScope.Children, windowCondition);
                    }, token);

                    // Iterate through all of the found Windows to find the one for MS Teams.
                    // Teams does NOT need to be the active window. It CAN be minimized to the system tray and it will still be found.
                    foreach (AutomationElement window in windows)
                    {
                        if (window.Current.Name.Contains("Microsoft Teams"))
                        {
                            // Store the window that belongs to Teams as teamsWindow
                            teamsWindow = window;
                            // Store the found Teams window AutomationElement
                            storedTeamsWindow = teamsWindow;
                            break;
                        }
                    }

                    if (teamsWindow == null)
                    {
                        isChecking = false;
                        return presenceStatus; // Return early if no Teams window is found
                    }
                }

                // Look for the presence status element within the Teams window
                var presenceElements = await Task.Run(() =>
                {
                    var presenceCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button);
                    return storedTeamsWindow.FindAll(TreeScope.Descendants, presenceCondition);
                }, token);
                foreach (AutomationElement element in presenceElements)
                {
                    // On my system, with the "new" Teams UI installed, I had to look for the string:
                    // "Your profile picture with status displayed as"
                    // and then look at the next word, which is my current status.
                    if (!string.IsNullOrEmpty(element.Current.Name) && element.Current.Name.Contains("Your profile picture with status displayed as"))
                    {
                        // Let's grab the status by looking at everything after "displayed as ", removing the trailing ".",
                        // and setting it to lowercase. I set it to lowercase because that is how I have my ESP32-C3 
                        // set up to read the data that this C# app sends to it.
                        int statusStart = element.Current.Name.IndexOf("displayed as ") + "displayed as ".Length;
                        presenceStatus = element.Current.Name.Substring(statusStart).Trim().Trim('"').Replace(".", "").ToLower();
                        break;
                    }
                }
            }
            catch (OperationCanceledException)
            {
                // Operation was cancelled
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Exception: {ex.Message}");
            }
            finally
            {
                isChecking = false;
            }
            // Return what we found
            return presenceStatus;
        }

I also have a timer that is running, which runs GetTeamsPresence every 30 seconds and sending the resulting presenceStatus over to my ESP32.

All in all, this has been running great for the past few days and has prevented my wife from bursting into my office while I am in the middle of a call.

Statocyst answered 25/6, 2024 at 17:14 Comment(1)
Wow, that's cool! This comes in the perfect moment for me, I updated the GitHub repo: github.com/miscalencu/OnlineStatusLight. Feel free to contribute next time!Quindecennial
O
0

What those posts are doing (certainly the first one, I didn't check the 2nd one) is calling the Microsoft Graph, which has a "presence" endpoint to get a user's status. There's actually even a specific "/me" endpoint, to get your own personal preference (less access rights needed). See more about this here: https://learn.microsoft.com/en-us/graph/api/presence-get?view=graph-rest-1.0&tabs=http

In order for this to work, as you've mentioned, you do need to have an Azure AD App registration. However, importantly, this will only require "delegated" permission (i.e. only permission from the single user, you, to access just data for that single user, you). As a result, you can use "delegated" and not "Application" permissions, which means that it does -not- require Admin consent for the tenant.

It -does- require and Azure Add Application though, at the risk of stating the obvious. While you don't have tenant admin rights, you need to see if you have Azure rights, just to create an application (you might have this anyway as a developer in your org). If you don't even have this, you can sign up for an M365 Developer account, and use that tenant. Importantly - the application does't have to be in the same tenant. If it's not, it's just a simple multi-tenant app, like any 3rd party Azure AD-backed application is.

Octosyllable answered 13/10, 2021 at 9:25 Comment(13)
Well, as I am already mentioning in my post, that requires that my app is registered in Azure AD and has the Presence.Read scope granted for MSGraph. Which is not possible since this is a corporate account and I don't have administrator privileges on that Azure AD instance to consent to it. Or am I missing somehting?Quindecennial
ah fair point, I didn't read well to spot that sufficiently. I've expanded the answer now - let me know if that covers what you need.Octosyllable
Thank you for the update, but unfortunately I cannot create apps in the main subscription. As a dev I use another subscription where my corporate user is a guest.Quindecennial
I discussed that in the last paragraph - it's totally fine if the app registration is in another tenant (in this case your Dev tenant). It's basically how -any- 3rd party M365 app works - the vendor registers the app in their own tenant, but the end user consents in their tenant. Essentially you just need AN Azure App Id, from any tenant. In practice this is fine in many cases, unless your main admins have turned off the ability for you to consent -any- app (more locked down environments will do this), but in that case even if the app was in your own tenant, you'd have trouble.Octosyllable
I already tried that and I failed with "AADSTS700016: Application with identifier 'xx' was not found in the directory 'yy'. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant". Could that be because in the "Supported account types -> Who can use this application or access this API?" under Authentication in my registered app I have "Accounts in this organizational directory only" selected and not "Accounts in any organizational directory"?Quindecennial
oh yes, that's definitely needed - I figured you'd done that but should have checked. As it implies, that's exactly what it's for. Once you've done that, when you try authenticate it in the app it will ask for consent (popup or redirect, depending on what you've implemented, e.g. msal or equivalent)Octosyllable
I will, I created a separate app in a separate subscription with authentication set to "Accounts in any organizational directory" but for some reason while the /me endpoint of the MsGraph works fine, the /me/presence shows a "User not found" message. Is really weird and I have all scopes, even the MS graph explorer fails. I suspect there is a sync issue since this is a new subscription and new AD with new users so I will give it some time and let you know how if it worked.Quindecennial
update 1/2: for some reason I have a problem to consent to Presence.Read permission with the guest user. Even though this permission does not required admin consent, I get the message: "APPNAME needs permission to access resources in your organization that only an admin can grant. Please ask an admin to grant permission to this app before you can use it.".Quindecennial
update 2/2: Could it be because I am not a verified publisher? I see this message in the API Permissions of my app: "Starting November 9th, 2020 end users will no longer be able to grant consent to newly registered multitenant apps without verified publishers." and I see this message in the consent screen error title: "APPNAME unverified". :(Quindecennial
I managed to get the Teams presence after all (even if consent was not given) but it shows the Presence in my organization, not the presence in the organization the guest user belongs to. So after all I don't think this can work.Quindecennial
That sounds a bit like you mixed up the tenants and users. You should Auth to your company tenant with your company account and just use the app from your tenant for the token generation. You can check the "tid" parameter in the token for example if you are authed to the correct tenant.Sikes
But if I do this (see 'update 1/2' above) I need an admin from my company to consent. So another dead end, isn't it?Quindecennial
I'm still looking into this - it might depend on certain more restrictive settings in your main tenant, where a user can't even consent to just basic user scopes. Will let you know if I learn more.Octosyllable

© 2022 - 2025 — McMap. All rights reserved.