Simultaneous connection to 2 different Azure tenants from 2 separate runspacePools not possible

LE SCOLAN Stephane OBS/GDO 0 Reputation points
2023-04-24T13:12:00.3166667+00:00
I am trying to establish 2 connections on 2 azure tenant in MicrosoftTeams (connect-MicroSoftTeams) in order to launch PowerShell commands on these Runspaces in asynchronous mode.
I want to establish the connections on these pools at initialization to avoid delays in establishing the connection. I am using Windows PowerShell SDK 3.0 and .Net 7.0.
 
The first connection from a pool is established normally and the response to a powershell command using that pool is indeed from the connected tenant.
 
The connection from a second pool to another tenant in MicrosoftTeams is also accepted.
 
However, the PowerShell command using this second pool will return a response from the first tenant.
 
As I do test it seems all the time it is the first tenant connect who answer even I connect this pool to another tenant.
 
Is there a way to force a response from the correct tenant and not only the first one connected?
 
We have a class **TeamsConnection** where I Intialize Runspacepool,create Runspace and connectTo Teams**

/// <summary>

/// Teams Connection class,manages and creates the instance for the first time and reuses the same object in the all calls
/// </summary>
public class TeamsConnection
{

    #region properties 
    private readonly RunspacePool _runspacePool;
    private readonly List<TenantRunSpace> _connectedRunspaces;
    private readonly IConfiguration _configuration;

    #endregion  
    /// <summary>
    /// TeamsConnection Constructor to Create Connection to Teams Tenant
    /// </summary>
    public TeamsConnection(IConfiguration configuration)
    {
        try
        {
            _configuration = configuration;
            _runspacePool = CreateRunSpacePool();
            _connectedRunspaces = OpenRunSpaces();
            ConnectToTeams();
        }
        catch (Exception exception)
        {
            Logging.LogError(MethodBase.GetCurrentMethod(), exception);
            throw;
        }
    }
Private function : **Intialize Runspacepool,create Runspace and connectToTeams**
 

/// <summary>

    /// Create PSCredential using username and password for Teams Tenant Connection
    /// </summary>
    /// <returns>PSCredential object for Teams Tenant Connection</returns>
    private PSCredential CreatePSCredentials(string userName, string password)
    {
        PSCredential pSCredential;
        try
        {
            SecureString secureString = new();
            Array.ForEach(password.ToArray(), secureString.AppendChar);
            secureString.MakeReadOnly();
            // SecureString secureString = GenerateTeamsSecureString();

            pSCredential = new(userName, secureString);
        }
        catch (Exception exception)
        {
            Logging.LogError(MethodBase.GetCurrentMethod(), exception);
            throw;
        }
        return pSCredential;

    }


    /// <summary>
    /// Create SecureString object to keep password of connection to Teams Tenant confidential
    /// </summary>
    /// <returns>SecureString Object</returns>
    private SecureString GenerateTeamsSecureString()
    {
        SecureString secureString = new();
        try
        {
            var password = _configuration.GetValue<string>(Convert.ToString(Constants.TeamsPassword)) ?? "";
            Array.ForEach(password.ToArray(), secureString.AppendChar);
            secureString.MakeReadOnly();
        }
        catch (Exception exception)
        {
            Logging.LogError(MethodBase.GetCurrentMethod(), exception);
            throw;
        }
        return secureString;

    }

    #endregion
}

/// <summary>

    /// Create a powershell runspacePool 
    /// </summary>
    /// <returns></returns>
    private RunspacePool CreateRunSpacePool()
    {
        // Use the ISS to set up the starting state you want
        // for runspaces in the runspace pool
        var iss = InitialSessionState.CreateDefault();

        // This call takes an array of module paths or names
        // if you want to import multiple modules, put them into an array and call this API once
        iss.ImportPSModule(new string[] { Constants.MicrosoftTeamsModuleName });

        // Mysteriously there's no overload here that allows you to set both the ISS
        // and the min/max runspace count without also providing a host.
        // But the default host is the internal DefaultHost.
        // So the best way to set the things we want without needing to provide a host is like this:
        RunspacePool rsPool = RunspaceFactory.CreateRunspacePool(iss);
        rsPool.SetMinRunspaces(1);
        rsPool.SetMaxRunspaces(5);
        rsPool.Open();

        return rsPool;
    }

/// <summary>

    /// Open a PowerShell runspace with MicrosoftTeams module
    /// </summary>
    /// <returns>A runspace object</returns>
    private List<TenantRunSpace> OpenRunSpaces()
    {
        List<TenantRunSpace> runSpaces = new();
        try
        {
            List<string> tenants = _configuration.GetSection("TenantIds").Get<string[]>().ToList();

            foreach (string tenant in tenants)
            {
                PowerShell pwsh = PowerShell.Create(RunspaceMode.NewRunspace);
                pwsh.RunspacePool = _runspacePool;
                TenantRunSpace runSpace = new TenantRunSpace() { Pwsh = pwsh, TenantId = tenant };
                runSpaces.Add(runSpace);
            }

        }
        catch (Exception exception)
        {
            Logging.LogError(MethodBase.GetCurrentMethod(), exception);
            throw;
        }

        return runSpaces;
    }


    /// <summary>
    /// Connect to Teams Tenant
    /// </summary>
    private void ConnectToTeams()
    {
        try
        {
            if (_connectedRunspaces != null)
            {

                List<TeamsConnections> teamsConnection = _configuration.GetSection("TeamsConnections").Get<List<TeamsConnections>>();

                foreach (TeamsConnections connection in teamsConnection)
                {

                    PSCredential pSCredential = CreatePSCredentials(connection.UserName, connection.Password);

                    Dictionary<string, object> parameters = new Dictionary<string, object>
                    {
                        { Constants.CredentialParameter, pSCredential }
                    };

                    Command commandObj = new(Constants.ConnectToMicrosoftTeamsCommand);
                    parameters.ToList().ForEach(parameter => commandObj.Parameters.Add(parameter.Key, parameter.Value));

                    TenantRunSpace? runspace = _connectedRunspaces.FirstOrDefault(item => item.TenantId != null ? item.TenantId.Equals(connection.TenantId) : false);

                    if (runspace != null)
                    {
                        PowerShell? pwsh = runspace.Pwsh;
                        PSCommand psCommand = new();
                        psCommand.Commands.Add(commandObj);
                        pwsh.Commands = psCommand;
                        pwsh.Invoke();
                    }

                }

            }
        }
        catch (Exception exception)
        {
            Logging.LogError(MethodBase.GetCurrentMethod(), exception);
            throw;
        }


    }
 
I send command to each MicrosoftTeams connection with this function :
 

/// <summary>

    /// Return the output of running the PowerShell Command
    /// </summary>
    /// <param name="command">Command that will be run by the PowerShell</param>
    /// <param name="parameters">Parameters of PowerShell command will be sent as keys of the dictionary and their values will be set to the values of the dictionary</param>
    /// <returns>Collection of PSObject returned from running the command of PowerShell</returns>
    public virtual (Collection<PSObject>, string) RunCommand(string command, string? tenantId, Dictionary<string, object>? parameters)
    {
        Collection<PSObject> psObjects = new();
        String instanceId = "";
        try
        {

            TenantRunSpace? runspace = _connectedRunspaces.FirstOrDefault(item => item.TenantId != null ? item.TenantId.Equals(tenantId) : false);

            if (runspace != null)
            {
                Command commandObj = new(command);
                if (parameters != null)
                {
                    parameters.ToList().ForEach(parameter => commandObj.Parameters.Add(parameter.Key, parameter.Value));
                }

                PowerShell? pwsh = runspace.Pwsh;
                instanceId = pwsh != null ? pwsh.InstanceId.ToString() : "";
                PSCommand psCommand = new();
                psCommand.Commands.Add(commandObj);
                pwsh.Commands = psCommand;

                psObjects = pwsh.Invoke<PSObject>();
            }            
        }
        catch (Exception exception)
        {
            Logging.LogError(MethodBase.GetCurrentMethod(), exception);
            throw;
        }
        return (psObjects, instanceId);
    }
 
I send a runcommand to do a get-CSCallQueue using:
 

public virtual async Task<(List<CsCallQueue>, string)> GetCsCallQueues(string? identity, string? nameFilter, string? tenantId, int offset, int limit)

    {
        List<CsCallQueue> csCallQueues;
        string poolInstanceId;
        try
        {
            Dictionary<string, object> filters = GetCommandParametersForGetCallQueues(identity, nameFilter, offset, limit);
            (Collection<PSObject> csCallQueuesPSObj, string instanceId) = _teamsConnection.RunCommand(Constants.GetCsCallQueuesCommand, tenantId, filters);
            csCallQueues = ClonePSColllectionToCsCallQueuesCollection(csCallQueuesPSObj);
            poolInstanceId = instanceId;
        }
        catch (Exception exception)
        {
            Logging.LogError(MethodBase.GetCurrentMethod(), exception);
            throw;
        }
        return (csCallQueues, poolInstanceId);
    }
When I send this command Get-csCallQueue (or other) to tenant, the good runspace is selected but the answer come only from the first tenant connect.
It Looks like same runspace is shared among all threads even if I created local runspace inside each thread.
 
Is someone have already do this and understand why we can't have an answer from the second tenant connect ?
Is there something link to DefaultRunspace or a specific way to select a connection to an azure tenant ?
 
Thanks.
Best regards.


I can't find a way to set DefaultRunspace or select individualy each connection to teams runspace.

I am trying to establish 2 connections on 2 azure tenant in MicrosoftTeams (connect-MicroSoftTeams) in order to launch PowerShell commands on these Runspaces in asynchronous mode and return each result in any web pages without been blocked by other request or Microsoft teams connection delay
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
11,092 questions
Microsoft Teams Development
Microsoft Teams Development
Microsoft Teams: A Microsoft customizable chat-based workspace.Development: The process of researching, productizing, and refining new or existing technologies.
3,390 questions
{count} votes

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.