I would like to intercept the MS Teams notifications about poor network quality, during calls - com

In order to diagnose a network condition I am trying to intercept (at least on Windows) the notifications about poor network quality that Teams pops-up sometimes, during a call.
For testing, I am using "clumsy", in order to generate iffiness in the network, while in a teams audiovideo call to my phone.
I was wondering if an API would provide a local interface to teams an allow me to subscribe to those events.
So far if found a type library in C:\Users\xxx\AppData\Local\Microsoft\TeamsPresenceAddin\Uc.tlb that looks promising because of the idl file that the oleviewer generates from it:
ModalityProperty in uc.tlb is an enum and contains these two values:
ucAVModalityAudioNetworkQuality = 0x300b001d,
ucAVModalityVideoNetworkQuality = 0x300b0021,
That enum is used by the interface _IAVModalityEvents and that looks to be fired by the coclass AVModality:
[
uuid(BA2BD6F3-7676-42E3-89C6-10CEB3F7E106),
helpstring("AVModality class defines the audio video modality.This class handles the events defined in the interface IAVModalityEvents."),
noncreatable,
custom(5047D0E3-86FD-4EB4-A500-AC4F5B4E17E1, "relns=Conversation.AudioVideo")
]
coclass AVModality {
[default] interface IAVModality;
interface IAVModality2;
**[default, source] dispinterface _IAVModalityEvents;**
};
Does anyone have an idea on how to connect to the running Teams instance (tried ROT but it doesn't seem to be registered) and how to enum modalities in order to get to the AVModality and subscribe to its events ?
Update 1:
I actually got some partial results with the Uc.tlb.
First I ran: TlbImp.exe Uc.tlb to create a C# assembly from it.
I then built the following C# app to intercept conversation changes:
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Xml;
using UCCollaborationLib;
class Program
{
//Teams CLSID harvested from registry
// Take a looksie at: https://stackoverflow.com/questions/56865704/com-object-for-teams-client-microsoft-office-uc
[ComImport, Guid("00425F68-FFC1-445F-8EDF-EF78B84BA1C7")]
public class TeamsOfficeIntegration
{
}
static void Main(string[] args)
{
var version = "15.0.0.0";
IUCOfficeIntegration teamsOfficeIntegration = (IUCOfficeIntegration) new TeamsOfficeIntegration();
if (teamsOfficeIntegration == null) {
Console.WriteLine("Cannot instantiate UCOfficeIntegration for Teams");
return;
}
IClient teamsClient = (IClient) teamsOfficeIntegration.GetInterface(version, OIInterface.oiInterfaceILyncClient);
if (teamsClient == null) {
Console.WriteLine("Cannot retrieve Teams Client interface");
return;
}
var teamsConversationManager = teamsClient.ConversationManager;
if (teamsConversationManager == null) {
Console.WriteLine("Cannot retrieve Teams ConversationManager interface");
return;
}
// Handle Teams Conversation Collection events
teamsConversationManager.OnConversationAdded += (ConversationManager _eventSource, ConversationManagerEventData _eventData) => { Console.WriteLine("Conversation added");};
teamsConversationManager.OnConversationRemoved += (ConversationManager _eventSource, ConversationManagerEventData _eventData) => { Console.WriteLine("Conversation removed");};
//iterate SelfParticipant or Modalities -> iterate IAVModality -> IConnectionPoint
}
}
The good news is that, when Teams is stopped it tries to start it so it does CoCreateInstance on the Teams LocalServer so it should be able to interact with it. The bad news is that even if the Teams app starts or is being attached to the creation fails with the following entry in the EventViewer/AdministrativeEvents:
"The server {00425F68-FFC1-445F-8EDF-EF78B84BA1C7} did not register with DCOM within the required timeout."

Related

Cannot create object as regular user/client from server running as service

I wrote a service which will run as the local system. The service has a COM object. I'd like to create and access that object from any local user's account.
Of course, it fails during a call to CoGetClassObject() in order to get the class factory. It fails with (debugger watch):
hr 0x80080005 : Server execution failed HRESULT
The ATL framework is registering the class factories on startup. I've stepped through the code and verified they are being registered with CLSCTX_LOCAL_SERVER and REGCLS_MULTIPLEUSE.
The service is running. I can't find any useful examples to solve this problem. I am thinking it is probably just a registry setting or possibly the way I call CoInitializeSecurity().
I call CoInitializeSecurity() like this:
class CCppServiceModule : public ATL::CAtlServiceModuleT< CCppServiceModule, IDS_SERVICENAME >
{
public :
DECLARE_LIBID(LIBID_CppServiceLib)
DECLARE_REGISTRY_APPID_RESOURCEID(IDR_CPPSERVICE, "{3a266de3-e432-4269-af44-5f76fdd0231f}")
HRESULT InitializeSecurity() throw()
{
// TODO : Call CoInitializeSecurity and provide the appropriate security settings for your service
// Suggested - PKT Level Authentication,
// Impersonation Level of RPC_C_IMP_LEVEL_IDENTIFY
// and an appropriate non-null Security Descriptor.
HRESULT _hr = S_OK;
_hr = CoInitializeSecurity((PSECURITY_DESCRIPTOR)&LIBID_CppServiceLib, -1, NULL, NULL, 0, RPC_C_IMP_LEVEL_IDENTIFY, NULL, EOAC_APPID, NULL);
return _hr;
}
// more stuff ...
};
In the above call, LIBID_CppServiceLib and the AppID of the service are the same...the wizard created them that way. Originally, I had a different call to CoInitializeSecurity() when I was registering solely as a local server and not a service. I could get it to run when registering as a local server, but I need it to run as a server under the System account.
The .rgs files for the service and the COM object look like this:
CppService.rgs:
HKCR
{
NoRemove AppID
{
ForceRemove {3a266de3-e432-4269-af44-5f76fdd0231f} = s 'CppService'
{
val LocalService = s 'CppService'
val ROTFlags = d '1'
val RunAs = s 'nt authority\system'
val AuthenticationLevel = d '1'
}
ForceRemove CppService.exe
{
val AppID = s '{3a266de3-e432-4269-af44-5f76fdd0231f}'
}
}
}
TestObject.rgs:
HKCR
{
CppService.TestObject.1 = s 'TestObject class'
{
CLSID = s '{7594592c-ddc5-47f3-a2df-9cc397988fd0}'
}
CppService.TestObject = s 'TestObject class'
{
CurVer = s 'CppService.TestObject.1'
}
NoRemove CLSID
{
ForceRemove {7594592c-ddc5-47f3-a2df-9cc397988fd0} = s 'TestObject class'
{
AppID = s '{3a266de3-e432-4269-af44-5f76fdd0231f}'
ProgID = s 'CppService.TestObject.1'
VersionIndependentProgID = s 'CppService.TestObject'
ForceRemove Programmable
LocalServer32 = s '%MODULE%'
{
val ServerExecutable = s '%MODULE_RAW%'
}
TypeLib = s '{3a266de3-e432-4269-af44-5f76fdd0231f}'
Version = s '1.0'
}
}
}
When I originally verified it worked when running as a local server, the CppService.rgs file was just a plain HKCR {} and the entry under CLSID for the object did not have the AppID key. As I said, originally it all worked fine when running as a local server, so the beginning COM plumbing worked fine, and I have many, many years working with COM. Maybe this isn't doable as COM. I could probably solve my problem with a WCF service in C#, but I wanted to do it in COM so I wouldn't have to listen on a port or named pipe.
I've seen comments in some places where people have registered the class factory in the ROT with the ROTREGFLAGS_ALLOWANYCLIENT flag, and then manually tried to connect to the class factory via the ROT. But, Microsoft's documentation says that my AppID value of ROTFlags being equal to 1 is supposed to have the same effect...but doesn't seem to.
I'm not calling CoInitializeSecurity() in the client application. Ideally, I'd like to be able to create with a simple vb script like:
set obj = CreateObject('CppService.TestObject')
Is this a solvable problem and I'm just missing something critical, or is it impossible?
Repo at: https://github.com/willcoxson/CppService

How to query for installed "packaged COM" extension points

I work on a plugin-based application that is currently scanning the Windows registry for compatible COM servers that expose certain "Implemented Categories" entries. This works well for "regular" COM servers installed through MSI installers.
However, I'm now facing a problem with COM servers installed through MSIX installers that expose COM extension points through the "Packaged COM" catalog as described in https://blogs.windows.com/windowsdeveloper/2017/04/13/com-server-ole-document-support-desktop-bridge/ . These COM servers can still be instantiated through CoCreateInstance, but RegOpenKey/RegEnumKey searches aren't able to detect their presence.
I'm not sure how to approach this problem. The best outcome would be some sort of Windows API for querying the "Packaged COM" catalog for installed COM servers that I can run in addition to the registry search. However, I don't know if that even exist? I'm also open for other suggestions, as long as they still allows my application to dynamically detect the presence of new COM-based plugins.
PLEASE DISREGARD THIS ANSWER. There's a better answer based on ICatInformation::EnumClassesOfCategories below.
Answering myself with sample code to query the "Packaged COM" catalog for installed COM servers. Based on suggestion from #SimonMourier.
using System.Collections.Generic;
using System.IO;
/** Use Target Framework Moniker as described in https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/desktop-to-uwp-enhance */
class PackagedComScan {
static void Main(string[] args) {
var packageManager = new Windows.Management.Deployment.PackageManager();
// this call require the "packageQuery" capability if called from a UWP app (add <rescap:Capability Name="packageQuery" /> to the appxmanifest)
IEnumerable<Windows.ApplicationModel.Package> my_packages = packageManager.FindPackagesForUser("");
foreach (var package in my_packages) {
try {
ParseAppxManifest(package.InstalledLocation.Path + #"\AppxManifest.xml");
} catch (FileNotFoundException) {
// Installed package missing from disk. Can happen after deploying UWP builds from Visual Studio.
}
}
}
static void ParseAppxManifest(string manifest_path) {
var doc = new System.Xml.XmlDocument();
using (var fs = new FileStream(manifest_path, FileMode.Open, FileAccess.Read, FileShare.Read))
doc.Load(fs);
var nsmgr = new System.Xml.XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("a", "http://schemas.microsoft.com/appx/manifest/foundation/windows10"); // default namespace
nsmgr.AddNamespace("com", "http://schemas.microsoft.com/appx/manifest/com/windows10");
// detect exported COM servers
var nodes = doc.SelectNodes("/a:Package/a:Applications/a:Application/a:Extensions/com:Extension/com:ComServer/com:ExeServer/com:Class/#Id", nsmgr);
foreach (System.Xml.XmlNode node in nodes)
System.Console.WriteLine("Exported COM CLSID: {0}", node.Value);
}
}
This is admittedly a bit ad-hoc since it relies on parsing the AppxManifest.xml files. Still, it seems to get the job done. Please note that UWP applications that runs within sandboxed AppContainer processes only seem to have read access to some of the AppxManifest.xml files, and not all. The code therefore only works for "regular" Win32 or .Net processes.
Answering myself with sample code to query all installed COM servers, including the "Packaged COM" catalog, using ICatInformation::EnumClassesOfCategories. Based on suggestion by Aditi_Narvekar:
#include <atlstr.h>
#include <vector>
static void CHECK(HRESULT hr) {
if (FAILED(hr))
abort(); // TODO: More graceful error handling
}
/** Return COM classes that implement any of the provided "Implemented Categories". */
inline std::vector<CLSID> GetClassesWithAnyOfCategories(std::vector<CATID> impl_categories) {
CComPtr<ICatInformation> cat_search;
CHECK(cat_search.CoCreateInstance(CLSID_StdComponentCategoriesMgr));
CComPtr<IEnumGUID> class_list;
CHECK(cat_search->EnumClassesOfCategories((ULONG)impl_categories.size(), impl_categories.data(), -1, nullptr, &class_list));
std::vector<CLSID> app_clsids;
app_clsids.reserve(64);
for (;;) {
CLSID cur_cls = {};
ULONG num_read = 0;
CHECK(class_list->Next(1, &cur_cls, &num_read));
if (num_read == 0)
break;
// can also call ProgIDFromCLSID to get the ProgID
// can also call OleRegGetUserType to get the COM class name
app_clsids.push_back(cur_cls);
}
return app_clsids;
}

How can find all devices (in an Azure IoT-Hub) where reported and desired properties differ?

I'm attempting to query an IoT-hub for devices where reported and desired properties differ. The purpose of this is to be able to notify users when devices fail to update for a given period of time. Running this query
SELECT * FROM c WHERE properties.desired != properties.reported
generates alot of false positives since both desired and reported contains a $metadata property with timestamps that always differ.
So to be clear, I want to list all devices where any of the "real" values (not metadata) differ from desired to reported.
The simply workaround to avoid the '$' properties for comparing between the desired and reported properties is to create a separate complex object within the desired and reported properties. This complex object will represent a state between the real and shadow device.
Example:
"Config": {
"abc": 123,
"status": "inprocess",
"battery": {
"level": 90
}
}
In this case, the query string for query all devices where their Config is different from desired to reported properties looks the following:
SELECT deviceId FROM devices WHERE is_defined(properties.desired.Config) and is_defined(properties.reported.Config) and properties.desired.Config != properties.reported.Config
UPDATE:
Another option (workaround) is using an Azure IoT Hub eventing for changes in the device twin. These notification changes can be routed to the custom endpoint e.g. Event Hub and consumed by EventHubTrigger function. The routing query:
is_object($body.properties.reported) OR is_object($body.properties.desired)
The function can easy obtained a device twin and comparing its properties such as desired and reported after their cleanup metadata parts.
The result of the comparing properties can be stored in the device twin tags, e.g.:
"tags": {
"Watchdog": {
"timestamp": "2019-08-12T14:24:36.1805155Z",
"status": "inprocess"
}
}
Note, that the tags property is not visible by device.
Once we have a watchdog status in the device tags, we can query devices for its status, e.g.:
"query": "SELECT deviceId FROM devices WHERE is_defined(devices.tags.Watchdog) and devices.tags.Watchdog.status='inprocess' "
The following code snippet shows an example of the function:
using Microsoft.Azure.Devices;
using Microsoft.Azure.EventHubs;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Text;
using System.Threading.Tasks;
namespace FunctionApp14
{
public static class Function2
{
static RegistryManager registryManager = RegistryManager.CreateFromConnectionString(Environment.GetEnvironmentVariable("AzureIoTHubShariedAccessPolicy"));
[FunctionName("Function2")]
public static async Task Run([EventHubTrigger("%myTwinChanges%", Connection = "myTwinChangesEventHubConnectionString", ConsumerGroup = "local")]EventData message, ILogger log)
{
var msg = Encoding.UTF8.GetString(message.Body.Array);
log.LogInformation($"C# IoT Hub trigger function processed a message: {msg}");
if (message.SystemProperties["iothub-message-source"]?.ToString() == "twinChangeEvents")
{
var twinChnages = JsonConvert.DeserializeAnonymousType(msg, new { properties = new JObject() });
if (twinChnages?.properties != null)
{
// deviceId
var connectionDeviceId = message.SystemProperties["iothub-connection-device-id"].ToString();
// device twin
var twin = await registryManager.GetTwinAsync(connectionDeviceId);
// cleanup and compare the twin properties
twin.Properties.Desired.ClearMetadata();
twin.Properties.Reported.ClearMetadata();
var desired = JObject.Parse(twin.Properties.Desired.ToJson());
var reported = JObject.Parse(twin.Properties.Reported.ToJson());
var status = JToken.DeepEquals(desired, reported) ? "ok" : "inprocess";
log.LogWarning($"desired-reported status = {status}");
// put the state on the tags
var twinPatch = JsonConvert.SerializeObject(new { tags = new { Watchdog = new { timestamp = DateTime.UtcNow, status = status } } });
await registryManager.UpdateTwinAsync(connectionDeviceId, twinPatch, twin.ETag);
}
}
await Task.CompletedTask;
}
}
}
You might need to do this in code. I had a piece of code that I used for something similar, I put it in a GitHub repo for this question.
The code is pretty naive, it compares the string values of the properties (excluding the metadata). It should be pretty easy to change it to check the property keys/values if needed.

Deploying SSRS RDL files from VB.Net - Issue with shared datasources

I am currently developing a utility to help automate our report deployment process. Multiple files, in multiple folders, to multiple servers.
I am using the reportservice2010.asmx web service, and I am deploying my files to the server - so most of the way there.
My issue is that I have shared data sets and shared data sources, which are deployed to individual folders, separate to the report folders. When the deployment occurs the web service looks locally for the data source rather than in the data source folder, giving an error like:
The dataset ‘CostReduction’ refers to the shared data source ‘CostReduction’, which is not
published on the report server. The shared data source ‘CostReduction’ must be published
before this report can run.
The data source/set has been deployed and the report functions correctly but I need to suppress these error messages as they may be hiding other actual errors.
I can hard code a lookup that checks if the data source/set exists and manually filter them via that, but it seems very in-efficient. Is there any way I can tell the web service where to look for these files or another approach that other people have used?
I'm not looking at changing the reports so the data source is read from
/DataSources/DataSourceName
as there are lots of reports and that's not how our existing projects are configured.
Many thanks in advance.
I realize you are using VB, but perhaps this will give you a clue if you convert it from C# to VB, using one of the translators on the web.
Hopefully this will give you a lead in the right direction.
When All the reports in a particular folder, referred to here as the 'parent folder', all use the same Shared Data source, I use this to set all the reports to the same shared Data Source (in this case "/DataSources/Shared_New")
using GetPropertiesSample.ReportService2010;
using System.Diagnostics;
using System.Collections.Generic; //<== required for LISTS
using System.Reflection;
namespace GetPropertiesSample
{
class Program
{
static void Main(string[] args)
{
GetListOfObjectsInGivenFolder_and_ResetTheReportDataSource("0_Contacts"); //<=== This is the parent folder
}
private static void GetListOfObjectsInGivenFolder_and_ResetTheReportDataSource(string sParentFolder)
{
// Create a Web service proxy object and set credentials
ReportingService2010 rs = new ReportingService2010();
rs.Credentials = System.Net.CredentialCache.DefaultCredentials;
CatalogItem[] reportList = rs.ListChildren(#"/" + sParentFolder, true);
int iCounter = 0;
foreach (CatalogItem item in reportList)
{
iCounter += 1;
Debug.Print(iCounter.ToString() + "]#########################################");
if (item.TypeName == "Report")
{
Debug.Print("Report: " + item.Name);
ResetTheDataSource_for_a_Report(item.Path, "/DataSources/Shared_New"); //<=== This is the DataSource that I want them to use
}
}
}
private static void ResetTheDataSource_for_a_Report(string sPathAndFileNameOfTheReport, string sPathAndFileNameForDataSource)
{
//from: http://stackoverflow.com/questions/13144604/ssrs-reportingservice2010-change-embedded-datasource-to-shared-datasource
ReportingService2010 rs = new ReportingService2010();
rs.Credentials = System.Net.CredentialCache.DefaultCredentials;
string reportPathAndName = sPathAndFileNameOfTheReport;
//example of sPathAndFileNameOfTheReport "/0_Contacts/207_Practices_County_CareManager_Role_ContactInfo";
List<ReportService2010.ItemReference> itemRefs = new List<ReportService2010.ItemReference>();
ReportService2010.DataSource[] itemDataSources = rs.GetItemDataSources(reportPathAndName);
foreach (ReportService2010.DataSource itemDataSource in itemDataSources)
{
ReportService2010.ItemReference itemRef = new ReportService2010.ItemReference();
itemRef.Name = itemDataSource.Name;
//example of DataSource i.e. 'itemRef.Reference': "/DataSources/SharedDataSource_DB2_CRM";
itemRef.Reference = sPathAndFileNameForDataSource;
itemRefs.Add(itemRef);
}
rs.SetItemReferences(reportPathAndName, itemRefs.ToArray());
}
}
To Call it I use this in the 'Main' Method:
GetListOfObjectsInGivenFolder_and_ResetTheReportDataSource("0_Contacts");
In this case "0_Contacts" is the parent folder, itself located in the root directory, that contains all the reports for which I want to reset their DataSources to the new Shared DataSource. Then that Method calls the other method "ResetTheDataSource_for_a_Report" which actually sets the DataSource for the report.

Azure MobileService in BackgroundTask

My question is pretty simple : Can we use MobileServices in BackgroundTasks? (WP 8.1 XAML)
I have an universal app connected to a MobileService from Azure and I wanted to let the BackgroundTask synchronize the data of my remote database.
So, is this possible?
public sealed class BackgroundTask : IBackgroundTask
{
public async void Run(IBackgroundTaskInstance taskInstance)
{
BackgroundTaskDeferral deferral = taskInstance.GetDeferral();
//Connect to mobile service and add data
deferral.Complete();
}
}
I can't add a reference to my WP project, so how can I get the App.MobileServiceClient?
My BackgroundTask project is a Windows runtime component.
EDIT:
I've added the reference to Windows Azure Mobile Service by managing nuGet Packages and I can now declare a mobileService but when I want to instanciate it I've got the following error :
BACKGROUNDTASKHOST.EXE' has exited with code 1 (0x1).
So this is what my code looks like now :
public sealed class BackgroundTask : IBackgroundTask
{
public async void Run(IBackgroundTaskInstance taskInstance)
{
BackgroundTaskDeferral deferral = taskInstance.GetDeferral();
Microsoft.WindowsAzure.MobileServices.MobileServiceClient lolServiceMobileClient =
new Microsoft.WindowsAzure.MobileServices.MobileServiceClient(
"https://myservicemobile.azure-mobile.net/",
"myKey");
deferral.Complete();
}
}
And this is the reference I've added :
Edit 2
I'm stills searching for solution and I'm now wondering, could it be because I exceed the amount of memory the background task is allowed to use?
I'm not an expert on background tasks, but you can indeed use the Mobile Services SDK there. Just make sure that the background task doesn't interfere with the app, by using an app setting to lock between the two processes.