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.