Microsoft/Office 365 PowerShell in Azure Function Apps – Teams Module

By | September 17, 2020

This will build on my multiple posts building up a template function in a function app that allows scripted control and reporting on Microsoft 365. This will be the last part of my template app as once we have AAD, Exchange, Teams and Graph capabilities we are pretty much complete. In honesty the graph part is here for future proofing – its a rapidly improving module that hopefully will eventually be able to do everything we still need to use the Skype Online (CSOnline) module for.

Previous posts are:
https://www.matts-stuff.co.uk/2020/07/27/microsoft-office-365-powershell-in-azure-functions/
https://www.matts-stuff.co.uk/2020/09/07/setting-up-an-aad-application-for-power-platform-function-apps/
https://www.matts-stuff.co.uk/2020/09/08/microsoft-365-powershell-in-azure-functions-exchange-management/
https://www.matts-stuff.co.uk/2020/09/11/microsoft-office-365-powershell-in-azure-functions-azuread-module-and-graph/

So the Teams module will be set up exactly the same way as the Exchange and AzureAD modules (linked above) to run in the AMD64 PowerShell instance. The main difference here is the source of the module files – Teams is rapidly improving (though still exceptionally burdened by having to use CSOnline for some critical tasks). To get the latest module files you are going to want to open Powershell 7 x86 (or at elast something after 4 so you have the -allowprelease parameter of install-module) and run:

Install-Module MicrosoftTeams -AllowPrerelease -Force -AllowClobber

This at the time of writing will run off and grab version 1.1.6 (Preview) version of the teams module. This module supports our cert-based service principal login and includes the New-CSOnlineSession cmdlet (which, sadly, doesn’t)

From here its all exactly as before. Run

Get-InstalledModule | Select Name, *installedl*

to get find the file lcoation for the module.

Jump into Azure Portal | Function Apps | <Your Function App> | Development Tools | Advanced Tools | Debug Console (Powershell) and create a new folder (“MicrosoftTeams”) in site/wwwroot and upload the entire module contents:

You will then want to go to Azure Active Directory and open your app registration and add any and all teams API permissons (Dont forget to grant them!)

NOTE – the module we are playing with is in preview and I have experienced some weirdness with permissions in some versions, such as in version 1.1.5 being able to read team members with Get-TeamUser but not read the actual team with Get-Team when logging in with the service principal configured exactly as above (which does work with some previous versions of the module and version 1.1.6). This will probably get shaken out in the long run but test everything.

Once we have the module uploaded and the app permissions ready to go we can import the module and connect to the teams powershell session:

        Import-Module "D:\home\site\wwwroot\Microsoftteams\microsoftteams.psd1"
        Connect-MicrosoftTeams -CertificateThumbprint $CertThumbprint -ApplicationId $AppID -TenantId $tenantid

The..ahem…’final’ version of my template app then looks like this:

# Input bindings are passed in via param block.
param($Timer)

$64bitPowerShellPath = Get-ChildItem -Path $Env:Windir\WinSxS -Filter PowerShell.exe -Recurse -ErrorAction SilentlyContinue | Where-Object {$_.FullName -match "amd64"}
$env:64bitPowerShellPath=$64bitPowerShellPath.VersionInfo.FileName

Function ConnectGraph($GraphURI, $Body, $Method) {
    $AppID="AppID"
    $CertThumbprint="certificate_thumbprint"
    $tenantid = "aad_tenantid"

    Import-Module "D:\home\site\wwwroot\MSAL\MSAL.PS.psd1"
    $cert = Get-Item "Cert:\CurrentUser\My\$($CertThumbprint)"
    $MSALToken = Get-MsalToken -ClientId $appID -TenantId $tenantID -ClientCertificate $cert
    If (!$Body -and $Method -eq "Get"){
        (Invoke-RestMethod -Headers @{Authorization = "Bearer $($MSALToken.AccessToken)" } -Uri $GraphURI -Method Get).value
    }
    ElseIf ($Body -and $Method -eq "Post") {

        (Invoke-RestMethod -Headers @{Authorization = "Bearer $($MSALToken.AccessToken)";'Content-type'="application/json" } -Uri $GraphURI -Method Post -Body $Body).value

    }
}

Function MailOut([string]$MailSubject, [string]$MailContent, [string]$MailSender, [string]$MailRecipient, $AttachmentString, [string]$AttachmentName) {
    $AttachmentBase64 = [Convert]::ToBase64String([System.Text.Encoding]::Utf8.GetBytes($attachmentstring))
    If($AttachmentString) {
        $OutMail = '{
            "message": {
                "subject": "'+$MailSubject+'",
                "body": {
                    "contentType": "Html",
                    "content": "'+$MailContent+'"
                },
            "toRecipients": [
                {
                    "emailAddress": {
                    "address": "'+$MailRecipient+'"
                    }
                }
            ],
            "attachments": [
                {
                    "@odata.type": "#microsoft.graph.fileAttachment",
                    "name": "'+$AttachmentName+'",
                    "contentType": "string",
                    "contentBytes": "'+$AttachmentBase64+'"
                }
                ]
            }
            }'
        }
    ElseIF (!$AttachmentString) {
        $OutMail = '{
            "message": {
                "subject": "'+$MailSubject+'",
                "body": {
                    "contentType": "Html",
                    "content": "'+$MailContent+'"
                },
            "toRecipients": [
                {
                    "emailAddress": {
                    "address": "'+$MailRecipient+'"
                    }
                }
            ]
            }
        }'
    }


    ConnectGraph -graphuri "https://graph.microsoft.com/v1.0/users/$MailSender/sendmail" -method "Post" -Body $OutMail

}
$script = {
    #Set variables
    $AppID="AppID"
    $CertThumbprint="certificate_thumbprint"
    $tenantdomain="starfleet.onmicrosoft.com"
    $tenantid = "aad_tenantid"

    #connection functions
    Function ConnectExchange {
        Import-Module "D:\home\site\wwwroot\ExchangeOnline\ExchangeOnlineManagement.psd1"
        Connect-ExchangeOnline -CertificateThumbprint $CertThumbprint -AppId $AppID -Organization $tenantdomain -Showbanner:$false
    }
    Function ConnectAzureAD {
        Import-Module "D:\home\site\wwwroot\AzureAD\AzureAD.psd1"
        Connect-AzureAD -CertificateThumbprint $CertThumbprint -ApplicationId $AppID -TenantId $tenantid
    }
    Function ConnectExchange {
        Import-Module "D:\home\site\wwwroot\ExchangeOnline\ExchangeOnlineManagement.psd1"
        Connect-ExchangeOnline -CertificateThumbprint $CertThumbprint -AppId $AppID -Organization $tenantdomain -Showbanner:$false
    }
    Function ConnectMicrosoftTeams {
        Import-Module "D:\home\site\wwwroot\Microsoftteams\microsoftteams.psd1"
        Connect-MicrosoftTeams -CertificateThumbprint $CertThumbprint -ApplicationId $AppID -TenantId $tenantid
    }


    #ConnectAzureAD
    #get-AzureADUser -objectid 

    #ConnectExchange
    #get-mailbox -Identity kirkjt@enterprise.starfleet.com 
    ConnectMicrosoftTeams
    Get-team -Displayname "Section 31"

}

$ScriptResult = (&$env:64bitPowerShellPath -WindowStyle Hidden -NonInteractive -Command $Script)
$ScriptResult
#$scriptresult | select DisplayName, WindowsEmailaddress| Export-Csv -Path ".\results.csv"


#$Results = Get-Content ".\results.csv" -Encoding UTF8 -Raw
#MailOut -MailSubject "Users from Graph" -MailContent "Graph Export Attached" -MailSender parist@deltaflyer.starfleet.com -MailRecipient siskob@demigods.com -AttachmentString $Results -AttachmentName "graphresult2.csv"

Youll see Ive just added in a new function to the script block for Powershell AMD64 – ConnectMicrosoftTeams and commented out the test stuff we did for Exchange and added a test to grab the details of a single Team with the Get-team cmdlet and return it as scriptresult. It worth oting the Teams module runs on x86 powershell – we COULD run it in the native instance with the graph module however I prefer to do all of the powershell bits together.

So (for now) that’s it. With this template and the module files you can do (almost) any regular reporting or configuration task. The are some exceptions, MSOL Service in particular and CSOnline. I have a script which has to use CSOnline so I’ll cover that very very shortly and I could use the same mechanism I used there to also use MSOLService but I dislike it – it uses a user identity instead of a service principal. Bleurgh.

Loading

Leave a Reply

Your email address will not be published. Required fields are marked *