MicrosoftTeams cmdlets not working with AccessToken

kshab 6 Reputation points
2021-04-21T19:38:22.627+00:00

I am trying to run cmdlets from powershell module MicrosoftTeams (version 2.0.0) in a C# web application. I am using Authorization code flow and code from the answer provided in this post to acquire token: Acquire AAD token using ASP.Net web forms. Note: I had changed resource in the code to graph.windows.net to acquire AAD token. Token is acquired by using AuthenticationContext.AcquireTokenByAuthorizationCodeAsync Method.

Once the token is acquired, I run the following lines to create a powershell instance in C# and to import MicrosoftTeams Module.

PowerShell pshell  
InitialSessionState iss;  
iss = InitialSessionState.CreateDefault2();  
iss.ImportPSModule(new[] { "MicrosoftTeams" });  
pshell = PowerShell.Create(iss);  

Then to connect with MicrosoftTeams, I run the following code:

var connectCmd = new Command("Connect-MicrosoftTeams");  
connectCmd.Parameters.Add("AadAccessToken", AccessToken);  
connectCmd.Parameters.Add("AccountId", "xxxxxxx@xxxxxx.onmicrosoft.com");  
pshell.Commands.AddCommand(connectCmd);  
var result1 = pshell.Invoke();  

Code works fine till here.

After this I clear the shell commands and invoke the Get-CsTeamsCallingPolicy cmdlet:

pshell.Commands.Clear();  
pshell.Streams.Error.Clear();  
  
pshell.AddScript("Get-CsTeamsCallingPolicy");  
var result2 = pshell.Invoke();  

After Invoke, I get an exception and this dialog pops up:

90101-getcsinternalaccesstoken.png

Pressing 'Continue' brings back the same dialogue a couple of times.

Exception details from this screen are:

System.Collections.Generic.KeyNotFoundException was unhandled by user code  
  HResult=-2146232969  
  Message=The given key was not present in the dictionary.  
  Source=mscorlib  
  StackTrace:  
       at System.Collections.Concurrent.ConcurrentDictionary`2.get_Item(TKey key)  
       at Microsoft.TeamsCmdlets.Powershell.Connect.Models.AzureSessionProvider.GetAccessToken(String resource, IEnumerable`1 scopes) in D:\a\1\s\src\Microsoft.TeamsCmdlets.PowerShell.Connect\Models\AzureSession.cs:line 80  
       at Microsoft.TeamsCmdlets.Powershell.Connect.TeamsPowerShellSession.GetAccessToken(String resource, IEnumerable`1 scopes) in D:\a\1\s\src\Microsoft.TeamsCmdlets.PowerShell.Connect\TeamsPowerShellSession.cs:line 82  
       at Microsoft.TeamsCmdlets.PowerShell.Connect.GetCsInternalAccessToken.ProcessRecord() in D:\a\1\s\src\Microsoft.TeamsCmdlets.PowerShell.Connect\GetCsInternalAccessToken.cs:line 61  
       at System.Management.Automation.CommandProcessor.ProcessRecord()  

After pressing continue for the 3rd time, control goes back to C# code, and I receive the following runtime exception:

Exception calling "GetSteppablePipeline" with "1" argument(s): "Exception calling "GetRemoteNewCsOnlineSession" with "1" argument(s): "Run either Connect-MicrosoftTeams or new-csonlinesession before running cmdlets.""   

Trying to run this logic from the powershell editor shows similar behavior:

Running the following two lines:

$AccessToken =  'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'   
Connect-MicrosoftTeams -AadAccessToken $AccessToken -AccountId 'xxxxxxx@xxxxxx.onmicrosoft.com'  

gives this result:

Account                         Environment Tenant                               TenantId                              
-------                         ----------- ------                               --------                              
xxxxxxx@xxxxxx.onmicrosoft.com AzureCloud  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx  

I then run Get-Team cmdlet:

Get-Team -User xxxxxxx@xxxxxxx.onmicrosoft.com  

which results in this message:

Get-Team : The given key was not present in the dictionary.  
At line:1 char:1  
+ Get-Team -User xxxxxxx@xxxxxxx.onmicrosoft.com  
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
    + CategoryInfo          : NotSpecified: (:) [Get-Team], KeyNotFoundException  
    + FullyQualifiedErrorId : System.Collections.Generic.KeyNotFoundException,Microsoft.TeamsCmdlets.PowerShell.Custom.GetTeam  

Running cmdlet Get-CsTeamsCallingPolicy yields this

Exception calling "GetSteppablePipeline" with "1" argument(s): "Exception calling "GetRemoteNewCsOnlineSession" with "1" argument(s): "Run either Connect-MicrosoftTeams or new-csonlinesession before running cmdlets.""  
At C:\Program Files\WindowsPowerShell\Modules\MicrosoftTeams\2.0.0\net472\SfBORemotePowershellModule.psm1:11369 char:13  
+             $steppablePipeline = $scriptCmd.GetSteppablePipeline($myI ...  
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
    + CategoryInfo          : NotSpecified: (:) [], ParentContainsErrorRecordException  
    + FullyQualifiedErrorId : CmdletInvocationException  

If I run Connect-MicrosoftTeams directly without providing access token and accountid, I get the login screens and after login everything works fine but not happening with AadAccessToken.

Same code works fine if connecting to AzureAD module via Connect-AzureAD cmdlet like this both in web application and powershell editor:

Connect-AzureAD -AadAccessToken $AccessToken -AccountId 'xxxxxxx@xxxxxxx.onmicrosoft.com'  

If someone has faced and successfully resolved this issue or have some tips on how to resolve this, please help.

I have already tried a lot of things including searching for the specific exception messages and any possible solutions but found nothing that could help in this particular scenario, installed the latest version of MSTeams module, the previous version was old and did not have all the cmdlets that I am looking to work with. I actually installed the preview version of MSTeams module also to see if this issue is fixed in the upcoming release. Uninstalled the deprecated SkypeForBuisnessOnline Connector module, updated windows and so on. If you look at Example 4 in the Microsoft documentation for Connect-MicrosoftTeams, this is what I am following.

Microsoft Graph
Microsoft Graph
A Microsoft programmability model that exposes REST APIs and client libraries to access data on Microsoft 365 services.
10,649 questions
ASP.NET
ASP.NET
A set of technologies in the .NET Framework for building web applications and XML web services.
3,273 questions
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.
10,279 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.
2,870 questions
Windows Server PowerShell
Windows Server PowerShell
Windows Server: A family of Microsoft server operating systems that support enterprise-level management, data storage, applications, and communications.PowerShell: A family of Microsoft task automation and configuration management frameworks consisting of a command-line shell and associated scripting language.
5,381 questions
0 comments No comments
{count} vote

2 answers

Sort by: Most helpful
  1. Aaron Miller 1 Reputation point
    2021-04-22T19:18:15.963+00:00

    You need an MSGraph token, not an AAD token. Here is some sample powershell I pieced together last week to do this. Notice: it uses the MSI of an Azure Function to access a Key Vault to get the secret for the AppID, and then generates a Graph token with the AppID and the secert. Also note the Connect-MicrosoftTeams command parameters. It took forever to figure those out :| Good luck!

    using namespace System.Net  
    using namespace System.Web  
      
    # Input bindings are passed in via param block.  
    param($Request, $TriggerMetadata)  
      
    # Define AppId, secret and scope, your tenant name and endpoint URL  
    $Scope = "https://graph.microsoft.com/.default"  
    $TenantName = "your tenant name here"  
    $AppId = "your appID here"  
    $AppSecretURI = "your vault secret URI here"  
      
    #Import-Module 'D:\Home\site\wwwroot\HttpTrigger1\Modules\MicrosoftTeams\2.1.0\MicrosoftTeams.psd1'  
      
    # Write to the Azure Functions log stream.  
    Write-Host "PowerShell HTTP trigger function processed a request."  
      
    # Interact with query parameters or the body of the request.  
    $name = $Request.Query.Name  
    if (-not $name) {  
        $name = $Request.Body.Name  
    }  
      
    $body = "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."  
      
    if ($name) {  
        # get access token with MSI. For more details, please refer to https://learn.microsoft.com/en-us/azure/app-service/overview-managed-identity#rest-protocol-examples  
        $tokenAuthURI = $Env:MSI_ENDPOINT + "?resource=https://vault.azure.net&api-version=2017-09-01"  
        $tokenResponse = Invoke-RestMethod -Method Get -Headers @{"Secret"="$env:MSI_SECRET"} -Uri $tokenAuthURI  
        $accessToken = $tokenResponse.access_token  
      
        # get secret value  
        $headers = @{ 'Authorization' = "Bearer $accessToken" }  
        $queryUrl = $AppSecretURI + "?api-version=7.0"  
        $keyResponse = Invoke-RestMethod -Method GET -Uri $queryUrl -Headers $headers  
        $AppSecret = $keyResponse.value  
      
        $Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"  
      
        # Create body  
        $Body = @{  
            client_id = $AppId  
            client_secret = $AppSecret  
            scope = $Scope  
            grant_type = 'client_credentials'  
        }  
        # Splat the parameters for Invoke-Restmethod for cleaner code  
        $PostSplat = @{  
            ContentType = 'application/x-www-form-urlencoded'  
            Method = 'POST'  
            # Create string by joining bodylist with '&'  
            Body = $Body  
            Uri = $Url  
        }  
      
        # Request the token!  
        $Request = Invoke-RestMethod @PostSplat  
        $Request  
        $token = $Request.access_token  
      
        Connect-MicrosoftTeams -TenantId $TenantName -AccountId $AppId -AadAccessToken $token -MsAccessToken $token   
      
        #Your Command Here  
        $team = Get-Team  
        Write-Host "Team Count to show connection success::::" ($team).count  
      
        $status = [HttpStatusCode]::OK  
        $body = "New Team Name is $team"  
    }  
    else {  
        $status = [HttpStatusCode]::BadRequest  
        $body = "Please pass a name on the query string or in the request body."  
    }  
      
    # Associate values to output bindings by calling 'Push-OutputBinding'.  
    Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{  
        StatusCode = [HttpStatusCode]::OK  
        Body = $body  
    })  
      
      
    
    0 comments No comments

  2. kshab 6 Reputation points
    2021-05-05T22:37:26.687+00:00

    Thanks for sharing the code. On running $team = Get-Team, I am receiving the following error:

    `Get-Team : Error occurred while executing 
    Code: Authorization_RequestDenied
    Message: Insufficient privileges to complete the operation.
    InnerError:
      RequestId: 6f51a09e-f4a1-44c0-9884-75f393508400
      DateTimeStamp: 2021-05-05T22:32:10
    HttpStatusCode: Authorization_RequestDenied
    At line:1 char:12
    +    $team = Get-Team
    +            ~~~~~~~~
        + CategoryInfo          : NotSpecified: (:) [Get-Team], ApiException
        + FullyQualifiedErrorId : Microsoft.TeamsCmdlets.PowerShell.Custom.ErrorHandling.ApiException,Microsoft.TeamsCmdlets.PowerShell.Custom.GetTeam`
    

    Any ideas on how to resolve this error?

    0 comments No comments