Recognize active team members with the CLI for Microsoft 365

Recognize active team members with the CLI for Microsoft 365 header image

Triggered by my previous blog to recognize contributions using the CLI for Microsoft 365 I got challenged on taking more metrics into account. Just using the activity details wasn’t enough according to Thomas Gölles. He pointed out that there are other metrics like replies or likes that might indicate how active a user is. Discussing possible solutions, we decided that some for of scoring should be possible. Hence a new blog with a slightly different approach.

Scenario

Starting with a new scenario; Identifying how active members are per team. And potentially sharing the top ‘n’ of those users. The goal is to identify those users based on their activity in Microsoft Teams. But potentially we should be able to branch out to other services like SharePoint as they might be active on files rather than using the chat functionality. Given that we want to identify those users within a timeframe the script should be able to handle activity within a timeslot.

Available metrics

The CLI for Microsoft offers a range of commands to work with teams, channels, and the data within Teams. The first step would be to identify what data we need. For demo purposes we start with the following:

  • Retrieve a single team
  • Retrieve all channels
  • Retrieve messages within a timeframe
  • Get all Likes & replies, and ofcourse likes per reply as well

If we do have that information, we can use that to group everything per user and score each interaction. We can even add additional points if the initial post has a subject to promote the use of best practices in working with Teams. Once we have all that information, we can post to Teams using an adaptive card.

Available commands

Interacting with the CLI for Microsoft will be done using a set of basic commands:

  • m365 teams channel list to list all teams channels
  • m365 teams message list to list all messages within a channel. We will use a filter to get only messages in our desired time frame.
  • m365 teams message reply list to list all replies per message as the the message list or message get do not provide you the reply list.
  • m365 aad user get to get the display name for each user.
  • m365 adaptivecard send to send our results to Teams and present a score card for all team members.

The script

The script itself list all channels, loops through those channels and retrieves all replies. It constructs a new object that saves the UserID and per message type some additional details. Once that information is processed the results are group per user and sorted by the scores. Those results are then posted to Teams using an adaptive card.

For scoring the scenario uses the following ‘ranking’:

  • Each new posts is worth 2 points.
  • Each new post with a subject scores an additional 1 pointer.
  • Each reply counts as 1 point.
  • Each reaction (using the 👍 or ♥ icons) scores a 0.5 points.

You can tweak those scores yourself if you like on line 77 to 80. Future samples might add SharePoint interactions as well.

$teamId = "<PUTYOURTEAMIDHERE>"
$webhookUrl = "<PUTYOURURLHERE>"
# You can get a delta of messages since the last 'n' days. Currently set to seven. You can go back a maximum of 8 months.
$date = (get-date).AddDays(-7).ToString("yyyy-MM-ddTHH:mm:ssZ")

$channels = m365 teams channel list --teamId $teamId --output json | ConvertFrom-Json
$results = @()
$scoreResults = @()

$channelCounter = 0;

foreach ($channel in $channels) {

    $channelCounter++;
    Write-Output "Processing channel... $channelCounter/$($channels.Length)"

    $messages = m365 teams message list --teamId $teamId --channelId $channel.id --since $date --output json | ConvertFrom-Json

    $messageCounter = 0;

    foreach ($message in $messages) {
        $messageCounter++
        Write-Output "Processing message ... $messageCounter/$($messages.Length)"

        # Skip messages that are created with an application (bots / adaptive cards)
        if ($null -ne $message.from.user.id) {
            $results += [pscustomobject][ordered]@{
                Type       = "Post"
                Details    = $message.reactionType
                UserId     = $message.from.user.id
                HasSubject = $($null -ne $message.subject)
            }
        }

        # Process all likes and comments on the initial message
        foreach ($reaction in $message.reactions) {
            $results += [pscustomobject][ordered]@{
                Type    = "Reaction"
                Details = $reaction.reactionType
                UserId  = $reaction.user.user.id
            }
        }

        $replies = m365 teams message reply list --teamId $teamId --channelId $channel.id --messageId $message.Id --output json | ConvertFrom-Json

        foreach ($reply in $replies) {
            # Skip replies that are created with an application (bots)
            if ($null -ne $message.from.user.id) {
                $results += [pscustomobject][ordered]@{
                    Type   = "Reply"
                    UserId = $reply.from.user.id
                }
            }

            # Process all likes and comments on the reply message
            foreach ($reaction in $reply.reactions) {
                $results += [pscustomobject][ordered]@{
                    Type    = "Reaction"
                    Details = $reaction.reactionType
                    UserId  = $reaction.user.user.id
                }
            }

        }
    }
}

# Group the results per user
$resultsGrouped = $results | Group-Object -Property UserId

#Score per user
foreach ($teamsUser in $resultsGrouped) {
    $user = m365 aad user get --id $teamsUser.Name --output json | ConvertFrom-Json

    # Count points
    # Each  post is two points, 1 extra point awarded for each Post with Subject
    # Each reply 1 and each reaction 0.5
    $score = (($teamsUser.Group | Where-Object { $_.Type -eq "Post" }).Count * 2)
    $score += (($teamsUser.Group | Where-Object { $_.HasSubject }).Count)
    $score += ($teamsUser.Group | Where-Object { $_.Type -eq "Reply" }).Count
    $score += (($teamsUser.Group | Where-Object { $_.Type -eq "Reaction" }).Count / 2)

    $scoreResults += [pscustomobject][ordered]@{
        DisplayName       = $user.displayName
        UserPrincipalName = $user.userPrincipalName
        Score             = $score;
    }
}

# Sort our score report based on the score
$scoreResults = $scoreResults | Sort-Object { $_.score } -Descending

#Construct adaptive card
$title = "🏆 Most active team members"
$scoreJson = '{   \"title\": \"🥇 '+$($scoreResults[0].DisplayName)+'\",   \"value\": \"' + $($scoreResults[0].score) + '\"   }'

if($scoreResults[1]){
    $scoreJson += ',{   \"title\": \"🥈 '+$($scoreResults[1].DisplayName)+'\",   \"value\": \"' + $($scoreResults[1].score) + '\"   }'
}
if($scoreResults[2]){
    $scoreJson += ',{   \"title\": \"🥉 '+$($scoreResults[2].DisplayName)+'\",   \"value\": \"' + $($scoreResults[2].score) + '\"   }'
}

$card = '{ \"type\": \"AdaptiveCard\", \"$schema\": \"http://adaptivecards.io/schemas/adaptive-card.json\", \"version\": \"1.2\", \"body\": [  {  \"type\": \"TextBlock\",  \"text\": \"' + $($title) + '\",  \"wrap\": true,  \"size\": \"Medium\",  \"weight\": \"Bolder\",  \"color\": \"Attention\"  },  {  \"type\": \"TextBlock\",  \"wrap\": true,  \"text\": \"Week ' + $(get-date -UFormat %V) + '\",  \"fontType\": \"Default\",  \"size\": \"Small\",  \"weight\": \"Lighter\",  \"isSubtle\": true  },  {  \"type\": \"FactSet\",  \"facts\": [   ' + $scoreJson + '  ]  } ] }'

m365 adaptivecard send --url $webhookUrl --card $card

Executing the script will tell you the progress as it can take a while to process all channels and messages depending the size of your team.

Script returning progress when processing channels and messages

Once the script is finished you can expect an adaptive card showing the score for that team. If no top 3 can be constructed it will only show the top 1 or 2, and their score.

Script returning progress when processing channels and messages

Hopefully this sample provides some insights in how you can track activity and recognize the contributions team members make per team. I love playing around with samples like this as its a great way to learn the commands and see what value you can add based off data and information already present in your Microsoft 365 tenant.

Loading comments…