Reinforcement Learning – An Introduction

Reinforcement Learning is teaching by example – it is how most of us learn. Reinforcement Learning (#RL) is a different approach to ML – it is a set of techniques that allows AI algorithms to experiment and learn from experience. RL falls in between supervised and unsupervised learning – there isn’t any labeled data, but at the same time it isn’t unsupervised either. At its most simple form, RL is a computational approach for automating goal-oriented decision making and learning.

Inherent RL is the ability to operate in a dynamic uncertain environment. RL can be more formally defined as the study, science, and problem of intelligence in the form of an agent that interacts in an environment. At the end of the day, almost all RL problems can be formalized as MDP (Markov decision processes).

The problem is represented by an environment – such as a world where an agent is based in. The steps in RL are quite clear – the agent takes actions, that have some effect on the environment. The environment acts on those actions and gives back an observation to the agent – what it sees and senses.

One special signal the environment gives back to the agent is called a reward signal. This signal is what an agent used to figure out how well it is doing. The RL problem is to take actions over time, to maximize the reward signals. And this notion of maximizing is what the agent is learning from the environment, without any explicit supervision. This construct helps an agent achieve a goal, even in an uncertain environment, factoring in delayed and indirect consequences of actions.

Reinforcement Learning Overview
Reinforcement Learning Overview

An agent can have many actions (i.e., choices); it uses a ‘reward’ signal to determine which of those actions is considered ‘good’ vs. ‘bad’. Of course, this determination is in the context of the outcome that we want to achieve.

Some examples of rewards in different industries and use cases:

  • Maneuvering a UAV’s – positive for following a chosen trajectory; negative for deviating from that trajectory.
  • Managing an investment portfolio – positive for each dollar earned; negative for each dollar lost.
  • Controlling a power station – As one can imagine, this control would typically constitute a few things in the environment – a sequence of controls, motors, batteries, power sources, etc. In optimizing the throughput of a power station, we can think of positive rewards for producing power; negative for exceeding a safety threshold.
  • Playing a game – positive for increasing score; negative for decreasing score.

Core concepts that make up RL:

Agent – The ‘thing’ that is using and acting on behalf of a user or another program. This can be a program executing a business process, a embedded process, the arm of a robot, actuators on a self-driving car controlling the wheels, etc.

Policy – A policy outlines how an agent would behave at certain times and can be thought of as the problem we are trying to solve. This is an agent’s behavior function and is a mapping of the business outcome that we are after.

Reward – A reward is a feedback special signal and outlines what is considered good (or bad) and is correlated with the agents’ current action, and the current state of the environment. All goals can be described as to maximize the cumulative reward. The reward is not a binary number but is a scaler between 0 and 1 – with zero being ‘bad’ and one being the best reward attainable for that action.

Value function – A value function represents how good is it to be in a particular state and related actions. Where a reward signal is showing the specification of good in an immediate sense (current step), the value function is representing the notion of good overall. At an abstract level, when thinking about the prediction of rewards, a rewards function is the primary, we can think of value functions as the secondary. In the end, we are more concerned with getting higher-value functions to make decisions, and not as much as higher rewards.

Model – A model is an agent’s view of the environment and mimics its behavior. This allows us to make inferences on how the environment will behave and is often used for planning. Think of the model as the strategy to use in solving the problem at hand.

Taxonomy of RL Algorithms

There are many types of RL algorithms (as we can see in the figure below), but these can broadly be classified in the following two categories.

  • Model free: A model-free algorithm can be thought of as an explicit trial and error algorithm. In a model free approach, the agent doesn’t have or ignores the environment; instead, the agent uses experience and tries to optimize a Policy.
  • Model based: On the other hand, a model-based algorithm reflects how an environment works, and factors that the associated reward functions and tries to maximize that. Technically, this is the optimization of the transition probability distribution of the MDP.

The main difference between the two – in one the algorithm optimizes for the environment, and in the other for a policy gradient. There is no one right or wrong algorithm – a lot of it depends on the situation at hand and what one is trying to optimize for.

As we can see below each of these categories can be further broken down – we won’t go into the details of those quite yet, maybe that is for another post. One of the most important components of most RL algorithms is a method to efficiently estimate values – at the end of the day, this is all about value estimation.

Chart showing the taxonomy of RL algorithms.
Taxonomy of RL Algorithms

Exploration and Exploitation

There are two concepts of exploration, and exploitation which are at odds with each other and for a given situation we should aim to get a balance of some sorts. In simple terms, RL is sequential decision making – one selects actions to maximize future rewards, and we need to plan long term – rewards might be delayed and not immediate, and we cannot be greedy. Sometimes, we need to sacrifice the immediate reward to gain more (or better) longer term rewards.

This can be thought of trial-and-error learning loop – with stream of experiences that constitute loops of actions, rewards, and observation. At the end of the day, this loop is what matters.

Exploration finds more information about the environment, and in doing so gives up rewards. Exploitation on the other hand, exploits the information it already has to maximize rewards. If we don’t exploit, we might be stuck in a sub-optimal place, and how would be know if there is a better sense or rewards without trying?

When we are the trial-and-error loop we might be losing rewards, and the agent needs to discover a good policy to maximize the rewards – this is the tension at the opposite ends of a string pulling each other.

It is important to balance both exploring and exploiting.

GPT-3 vs other AI powered assistants

I been kicking the tires with Open AI’s #GPT-3. Based on the screenshot below, it might be easy to think “oh boy does the model think highly of itself”, but as with most things in life – devil is in the details.😃 The screenshot below was a forked version of davinci engine and follows the Q&A structure.

OpenAI's GPT3 answering questions when compared to other AI powered assistants.
GPT-3 vs other AI assistants

Using OpenAI’s API is quite simple; perhaps too simple! It is quite easy to unleash the beast as the code snippet shown below. If you are new to using GPT3, I would highly recommend you start with the use case model guidelines first.

In the context of a toy example, to get to a simple Q&A chatbot as the screenshot earlier shown is quite simple. The API is powerful, and simple to use, and getting started is easy as the code below shows.

import os
 import openai
 openai.api_key = os.getenv("OPENAI_API_KEY")
 response = openai.Completion.create(
   prompt="I am a highly intelligent question answering bot. If you ask me a question that is rooted in truth, I will give you the answer. If you ask me a question that is nonsense, trickery, or has no clear answer, I will respond with \"Unknown\".\n\nQ: What is human life expectancy in the United States?\nA: Human life expectancy in the United States is 78 years.\n\nQ: Who was president of the United States in 1955?\nA: Dwight D. Eisenhower was president of the United States in 1955.\n\nQ: Which party did he belong to?\nA: He belonged to the Republican Party.\n\nQ: What is the square root of banana?\nA: Unknown\n\",

There are three core concepts when using GPT-3: Prompt, Completion, and Tokens.

To start using the API, we need to start giving it some prompts – this provide some context to the engine on what is expecting. Without the surface area is too broad and we get into nonsensical situations. This is part of the task-specific fine-tuning required.

Think of when giving examples as part of the prompt, we are essentially “programming” the model and providing guidance and providing some hints to context and pattern matching. Note the training data cut off in late 2019, so the model in production today doesn’t have access to data and events post that (e.g., Covid).

Completion is the output that GPT3 generates based on the prompt. To be clear, this is not the full text but is the predicted completions; think of it as “autocomplete” in Word, or Outlook or a search engine. The API has flexibility to return more than one predicted completion along with the probabilities of alternative tokens at each position (to me it seems just like the wave function when thinking of Quantum mechanics 🐼).

Finally, think of Token are the smaller Lego blocks that combine to make words. The API, which is nothing but wrappers around GPT-3 breaks up the text into tokens before processing it. The GPT-3 model understands the statistical relationships between these tokens and uses this to produce the next token in a sequence of tokens.

For example, if we are curious about Tokens, we can see in the screenshot below how the API “tokenizes” this paragraph and get the details of the tokens. This paragraph contains 207 characters and 43 tokens.

Token text that GPT-3 API converts to before using.
GPT-3 Tokens – Text
Token ID's that GPT-3 API converts to before using
GPT-3 Token – IDs

At a high level, think of one token == ~4 characters of text, which is ¾ of a word; so, 100 tokens ~= 75 words.

This is just dipping our toes in the beast that is GPT-3; the API’s which wrap up and expose the engines (more on that in another post) make it simple to use and without getting too much in the weeds of 175 billion parameters. 🙂

ML algorithm cheat sheet

A #ML algorithm cheat sheet – helping narrow down to a certain set of #algorithm grouping depending on the problem at hand and what we are trying to solve from a business perspective.

Cheat sheet showing different #ML algorithms to choose from depending on the task at hand
Figure 1

Figure 2 shows what additional characteristics we need to consider when choosing the right ML algorithm for your situation at hand. This is something that cannot be generic and is very situational.

Flow diagram showing how to select a ML algorithm and additional characteristics we need to consider as we select a ML algorithm
Figure 2 – Characteristics in selecting ML algorithms

If you find this useful, I would also recommend reading “How to select algorithms” which is detailed as part of Azure ML designer.

Auto-update PowerShell and nag-free

If you are like me and get annoyed with the big PowerShell upgrade ‘nag’ ‘reminder’ (see screenshot below); instead of trying to figure out what to download and install the update, there is a simpler way to get the latest update and address the nag. 🙂

bah ree 
powershell 7.e.2 
Copyright Cc) Microsoft Corporation . 
https :// aka. ms/powershell 
Type 'help' to get help. 
All rights reserved. 
A new PowerShell stable release is available: v7.€.3 
Upgrade now, or check out the release page at: 
https :// aka . 9.3 
Loading personal and system profiles took 1€89ms. 
) iex 
$Cirm https ://—powershell.psl) } —UseMSI" 
VERBOSE: About to download package from 'https 
e msi ' 
[€9 : 41]

You can just run the code below in an elevated prompt to get the latest release of PowerShell – it is easy-peasy. 🙂

iex "& { $(irm } -UseMSI"

Changing Window Terminal’s default directory

If you are like me, and don’t really have your work saved in the “%USERPROFILE%” it gets annoying after a time, to keep changing the directory.

If there is one specific folder that you prefer, it is an easy configuration change in the profile setting – add a setting called “startingDirectory” and point it to the path you want.

For example, I have a root folder called “src” where most of the code I am working on sits, and that’s where I wanted to default the terminal to.

To get to the profile, you can either use the shortcut CTRL+, or from the dropdown in the title bar, click settings (see below). This will open the settings.json in your default editor.

Terminal setting

In my case, I wanted the starting directory for all the shells, so I put it under “defaults” – you can choose different options for different shells, and then would have this in the appropriate shell’s settings and not the default block of course.

Below is what this looks like for me pointing this to “c:\src”. Also note, the escape characters need to be formatted properly to parse.

    // Put settings here that you want to apply to all profiles.
    "fontFace":  "CaskaydiaCove NF",
    "startingDirectory": "c:\\src",
setting.json screenshot

Once you save the file, it should automatically reload the terminal. And if the json didn’t parse – because of a typo or a syntax error then you would see an error similar to the one shown below.

Parsing error

In this example, I set the starting folder as “c:\src”; instead of “c:\\src”.

bfloat16 – how it improves AI chip designs

Floating point calculations are slow for computers (specifically CPUs); possibly representing the same struggle for many humans. 🙂

I remember a time when a FPU (floating point unit) was an upgrade and one had to pay extra to get one. Very useful when you needed that extra precision in computing – and in my head, it always seemed like the Turbo button. 🙂

For most #ML workloads and computations, precision isn’t the most important criteria; with every increasing data and parameters (looking at you GPT-3 with 45 TB of data and 175 billion parameters!), what most ML needs today is speed and dynamic range.

This is where bfloat16 (Brain floating-point format with 16 bits) – a new floating-point format comes handy and in the context of #AI improves on IEEE 754 – the current floating-point arithmetic standard.

As per IEEE 754, a floating point it will always take up 32 bits (see Figure 1 below) – irrespective of the size of the number. The exponent (8 bits) tells us how many numbers we shift (left or right) and place the decimal. The fraction (23 bits), also called the mantissa, holds the actual number – i.e. the data.

Figure 1 – IEEE 754 Floating point representation

bfloat16 truncates the data size in a third (see Figure 2) – with the fraction truncated from 23 to 7 bits. This of course means bfloat16 isn’t as precise. However bfloat16 has the same exponent bits as IEEE-754 it can represent a similar range (small to large), but more importantly are easier to convert between bfloat16 and IEEE 754.

Figure 2 – fbloat16 representation

Less precision doesn’t impact the matrix multiplication as much so in the context of ML training and inference these chips at scale are more efficient – not only they are faster, they also use less power, and memory bandwidth.

What is interesting in some neural nets such as a DNN, these less precision bfloat16 are more precise compared to IEEE 754! This is because the regularization and quantization weights cannot use the finer precision represented by IEEE 754 but adapt better with bfloat16. 🙂

Finally, bfloat16 is not a universal standard (yet); most AI chips support this. ARM, Intel, and, AMD have started adding support for this in their chipsets.

WSL2 +Ubuntu on Window 10 (2004)

One of the key advances in the latest version of Windows 10 (2004) is WSL2 (Windows Subsystem for Linux v2) – and whilst a version bump, it offers so much more. This allows us to run with near-native performance linux binaries (ELF64).

Before we get into the steps outlined to install WSL2, I also recommend installing Windows Terminal, and winget. Although not required, it does make it simpler to use and a better (dev) experience – especially when setting up a new workstation.

For WSL2 to work, you need to make sure you are on Windows 10 2004 Build 19041 or higher. If you don’t have this, run Windows update and see if that updates your OS. If that doesn’t offer a update, you could also try the Windows update assistant.

To get WSL2, whilst not complicated one needs to do the following steps, in this order – running the commands in an elevated prompt.

  1. Enable the Windows Subsystem for Linux optional feature.
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
  1. Enable the Virtual machine platform optional feature.
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
  1. Reboot
  2. Run Windows update (and reboot again if there are updates)
  3. Set WSL2 as your default option.
wsl --set-default-version 2
Administrator: Windows X v 
Administrator: powerf X 
PS C: Bahree> dism.exe /ontine /enabte—feature /featurename:Microsoft—Windows—Subsystem—Linux /att /norestart 
Deployment Image Servicing and Management toot 
Image Version: 
Enabling feature(s) 
The operation completed successfully. 
PS C: Bahree> dism.exe /ontine /enabte—feature /featurename:virtualmachineptatform /att /norestart 
Deployment Image Servicing and Management toot 
Image Version: 
Enabling feature(s) 
The operation completed successfully. 
PS C: Bahree> dism.exe /ontine /enabte—feature /featurename:virtualmachineptatform /att /norestart 
Deployment Image Servicing and Management toot 
Image Version: 
Enabling feature(s) 
The operation completed successfully.
Enabling WSL2
  1. Install your Linux distro of choice. You can do this via Store, or via winget, such as Ubuntu using the following command.
winget install -e --id Canonical.Ubuntu
PS C: Bahree> winget install —e ——id Canonical. Ubuntu 
Found Ubuntu [Canonical . Ubuntu] 
This application is licensed to you by its owner . 
Microsoft is not responsible for, nor does it grant any Licenses to, 
Successfully verified installer hash 
Starting package install... 
Successfully installed . 
third—party packages .
Installing Ubuntu via winget

Note, when trying to set WSL2 as the default option above (Step 5) and you get a error 0x1bc, that most likely means you need to run Windows update and reboot.

PS C: wsI 
Error: exlbc 
For information on key differences with 
WSL 2 please visit 
PS C: wsI 
Windows Subsystem for Linux has no installed distributions. 
Distributions can be installed by visiting the Microsoft Store: 
https : / /aka. ms/wslstore
WSL Error

And here is my running Ubuntu and updating it.

Installing, this may take a few minutes. . 
Please create a default UNIX user account. The username does not need to match your Windows username. 
For more information visit: 
Enter new UNIX username: amit 
New password: 
Retype new password:
Installing Ubuntu
$ sudo apt updaze sudo apt upgrade 
[sudo] password for amit: 
1 http://security.ubuntu . com/ubuntu focal-security InRe1ease [187 ka] 
et:2 http://archive.ubuntu . com/ubuntu focal InRe1ease [265 ka] 
http://security.ubuntu . com/ubuntu focal -security/main amd64 Packages [147 ka] 
http://archive.ubuntu . com/ubuntu focal -updates InRe1ease [111 ka] 
http://archive.ubuntu . com/ubuntu focal -backports InRe1ease [98.3 ka] 
http://security.ubuntu . com/ubuntu focal-security/main Translation-en [51.8 ka] 
http://security.ubuntu . com/ubuntu focal-security/main amd64 c-n-f Metadata [3432 B] 
http://security.ubuntu . com/ubuntu focal -security/restricted amd64 Packages [28.9 ka] 
http://security.ubuntu . com/ubuntu focal-security/restricted Translation-en [7664 B] 
http://security.ubuntu . com/ubuntu focal-security/restricted amd64 c-n-f Metadata [324 B] 
http://security.ubuntu . com/ubuntu focal-security/universe amd64 Packages [42.8 ka] 
http://security.ubuntu . com/ubuntu focal-security/universe Translation-en [22.6 ka] focal-security/universe amd64 c-n-f Metadata [1768 B] 
http://security.ubuntu . com/ubuntu focal-security/multiverse amd64 Packages [1172 B] 
http://archive.ubuntu . com/ubuntu focal/main amd64 Packages [978 ka] 
http://security.ubuntu . com/ubuntu focal-security/multiverse Translation-en [548 B] focal-security/multiverse amd64 c-n-f Metadata [116 B] 
Get : 
Get : 3 
Get . 
Get . 
Set : 8 
Get . 
• 18 
• 11 
Get . 
• 12 
• 13 
iGet : 14 
Get . 
• 16 
Get . 
• 17 
• 18 
• 19 
Get . 
Get . 
• 21 
• 22 
• 23 
Get . 
• 24 
Get . 
• 25 
Get . 
• 26 
• 27 
• 28 'Ittp• 
http:/'archive.ubuntu . com/ubuntu 
http:/'archive.ubuntu . com/ubuntu 
http:/'archive.ubuntu . com/ubuntu 
http:/'archive.ubuntu . com/ubuntu 
http:/'archive.ubuntu . com/ubuntu 
http:/'archive.ubuntu . com/ubuntu 
http:/'archive.ubuntu . com/ubuntu 
http:/'archive.ubuntu . com/ubuntu 
http:/'archive.ubuntu . com/ubuntu 
http:/'archive.ubuntu . com/ubuntu 
. / 'archive. ubuntu . com/ubuntu 
focal/main Translation -en [SB6 ka] 
focal/main amd64 c-n-f Metadata [29. S ka] 
focal/universe amd64 Packages [8628 ka] 
focal/universe Translation-en [5124 ka] 
focal/universe amd64 c-n-f Metadata [265 ka] 
focal/multiverse amd64 Packages [144 ka] 
focal/multiverse Translation-en [184 ka] 
focal/multiverse amd64 c-n-f Metadata [9136 B] 
focal -updates/main amd64 Packages [312 ka] 
focal -updates/main Translation-en [116 ka] 
focal-u dates/main amd64 c -n-f Metadata 7756 B
Updating Ubuntu

So, what’s the big deal? This is where it gets quite interesting and one simple example is the windows interoperability with Linux – allowing one to run linux commands from within a command prompt.

Mixing Linux and Windows commands

Getting list of users from Microsoft Teams

I recently needed to get a list of users that belong to a specific Microsoft Teams team – and there isnt anything out of the box to get this using the Teams app. AFAIK, the only way to do this is using the Microsoft graph API – for which there are a few options.

For something quick (e.g. getting a list of users in a team), using the Graph explorer could be easy enough. On the other hand, if you need something more robust, you should program against the (REST) API.

Graph Explorer

Navigate to Graph explorer, sign in and authenticate yourself against the specific O365 tenant you are interested in – most folks would only have one.

Microsoft Graph Explorer

Once authenticated, on the panel on the left you see several sample queries and scroll down until you see the Teams.

Teams sample queries

To get members of a specific team, you need to get the team ID for that Team. This is unique ID (GUID) and doesn’t change over the lifetime of the team. If you have this, then go ahead to the next section – Getting team members.

Getting a list of Teams and Team ID

On the query panel in Graph explorer, select the “my joined teams” and run the query. You will get a JSON back that contains the list of teams that you are a member of. The “id” element represents the Team ID which you would need for any team related API calls. For example, I am interested in this specific #AI team: “#Reinforcement Learning and Decision AI”.

Get team details

Getting team members

Once you have the Team ID (the unique GUID that each identifies each team), you can get the members of the team using that option on the left. As shown on the screenshot below, you do need to pass in the team ID to the REST API and this would be something like this (and don’t worry what I am showing below is a fictious GUID): 
Member details for a specific Microsoft Team team

Programmatically getting Microsoft Team details

If you want something more robust and repeatable, then using the API (via code) or PowerShell might be better. If you are programming, you will need to register an app – which can authenticate using the Identify platform. This of course is quite powerful, but at times for simple things might be a bit too much.

In my simple task to get users from Teams, I prefer the PowerShell option. To get this going first you need to install the MicrosoftTeam module. This can be done using the command below.

Install-Module -Name MicrosoftTeams

Depending on your configuration you might get a warning as shown below.

PowerShell module installation

Once the Teams PowerShell module is installed, you can run PowerShell scripts against Teams and achieve the same result. I have two scripts below showing the same steps as with the Graph Explorer above. One of these gets details of the teams that a user is a member of. And the second script is to get members of a selected team.

Using PowerShell to get Team Details

The PowerShell script below to get a Team details is below; you can also get it from GitHub. Before you run this, there are two variables that need to be set.

  • One, the path where you want the team details to be exported (this is a csv file).
  • Two, set the email that you will use. This needs to be the same one that you authenticated against.

You will be prompted to sign in to authentic and this should be an experience that most folks would be familiar with. Note, each time you run the script, you need to authenticate – and this is irrespective of say if you are already logged into Teams of Office 365.

Authenticating user against Office 365

Assuming you have authenticated successfully, you should see an output like the one shown below; and a csv file in the path you configured will be created. This file will always be overwritten – without any prompts (of course this is assuming no other process is open that has a lock on that file).

#Set these variables, to what makes sense in your situation. The email here is the one that is the one connected to your teams account.
$exportLocation = "C:\temp\team-details.csv"
$emailAddress = ""

#Authenticate against teams

Write-Host -ForegroundColor Blue "Successfully connected to Teams"
Write-Host -ForegroundColor Blue "Getting all team details for user: $($emailAddress)"
Write-Host -ForegroundColor Blue "Please be patient, if there are a lot of teams, this can take a while..."

# Get all of the team Groups IDs
# $GetUsersTeams = (Get-Team).GroupID
$GetUsersTeams = Get-Team -User $emailAddress

$Report = @()

# Will hold a basic count of user types and teams
$unavailableTeamCount = 0

# Loop through all teams that the user belongs to
$currentIndex = 1

ForEach($thisTeam in $GetUsersTeams) {
	# Show some output to the user
    Write-Progress -Id 0 -Activity "Building report from Microsoft Teams" -Status "$currentIndex of $($GetUsersTeams.Count)" -PercentComplete (($currentIndex / $GetUsersTeams.Count) * 100)
    # Attempt to get team details, throw error message if no access
    try {
        # Get team members
        #$users = Get-TeamUser -GroupId $thisTeam.groupID

		# Create an object to hold all values
        $teamReportObject = New-Object PSObject -Property @{
                GroupID = $thisTeam.GroupID
				TeamName = $thisTeam.DisplayName
                Description = $thisTeam.Description
                Archived = $thisTeam.Archived
                Visibility = $thisTeam.Visibility
				eMail = $thisTeam.MailNickName

            # Add to the report
            $Report += $teamReportObject
    } catch [Microsoft.TeamsCmdlets.PowerShell.Custom.ErrorHandling.ApiException] {
        Write-Host -ForegroundColor Yellow "No access to $($team.DisplayName) team, cannot generate report"
Write-Progress -Id 0 -Activity " " -Status " " -Completed

# Disconnect from teams

# Provide some nice output
Write-Host -ForegroundColor Green "============================================================"
Write-Host -ForegroundColor Green "                Microsoft Teams User Report                 "
Write-Host -ForegroundColor Green ""
Write-Host -ForegroundColor Green "  Count of All Teams - $($GetUsersTeams.Count)                "
Write-Host -ForegroundColor Green "  Count of Inaccesible Teams - $($unavailableTeamCount)         "
Write-Host -ForegroundColor Green ""

$Report | Export-CSV $exportLocation -NoTypeInformation -Force
Write-Host -ForegroundColor Blue "Exported report to $($exportLocation)"

Getting Team members using PowerShell

Now that you have the Team ID you are interested, you can run the other PowerShell script (also available on GitHub) to get a list of all the users in a specific team. Like the previous script, you would need set a couple of variables in the script:

  • The Team ID for the team you are interested in.
  • Path for the csv file with details to be saved.

Once you have authenticated and ran the script, the output will look like the one shown below. You get a summary of the team details, and details of the Teams users and their type (owner, member, or guest). And just like earlier, the file will be overwritten without a prompt, assuming no locks on it.

Members of a Microsoft Team
#Global variables to set:
#path of the file where to export
#specific ID of the team that you want the users for. 
$exportLocation = "C:\temp\RL-decision-AI-export.csv"
$TEAM_ID = "f3f9ad1f-beea-4026-9b86-dd3788404999"
$Report = @()

# counters
$ownerCount = 0
$memberCount = 0
$guestCount = 0

#connect to teams

$team = Get-Team -GroupId $TEAM_ID

#Patience, supposed to be a virtue
Write-Host -ForegroundColor Blue "Successfully connected to Team: $($team.DisplayName)"
Write-Host -ForegroundColor Blue "Getting all users in the team"
Write-Host -ForegroundColor Blue "Please be patient, if there are a lot of users, this can take a while..."

# Attempt to get team users, throw error message if no access
try {
	# Get team members
	$users = Get-TeamUser -GroupId $team.groupID

	# Loop through and get all the users
	$currentIndex = 1
	# foreach user create a line in the report
	ForEach($user in $users) {
		# Show some output to the user
		Write-Progress -Id 0 -Activity "Generating user report from Teams" -Status "$currentIndex of $($users.Count)" -PercentComplete (($currentIndex / $users.Count) * 100)
		# Maintain a count of user types
		switch($user.Role) {
			"owner" { $ownerCount++ }
			"member" { $memberCount++ }
			"guest" { $guestCount++ }

		# Create an object to hold all values
		$ReportObject = New-Object PSObject -Property @{
			User = $user.Name
			Email = $user.User
			Role = $user.Role

		# Add to the report
		$Report += $ReportObject
catch [Microsoft.TeamsCmdlets.PowerShell.Custom.ErrorHandling.ApiException] {
	Write-Host -ForegroundColor Yellow "No access to $($team.DisplayName) team, cannot generate report"

#Complete progress
Write-Progress -Id 0 -Activity " " -Status " " -Completed

# Disconnect from teams

# Write out details for the user
Write-Host -ForegroundColor Green "============================================================"
Write-Host -ForegroundColor Green "                Microsoft Teams User Report                 "
Write-Host -ForegroundColor Green ""
Write-Host -ForegroundColor Green "Team Details:"
Write-Host -ForegroundColor Green "Name: $($team.DisplayName)"
Write-Host -ForegroundColor Green "Description: $($team.Description)"
Write-Host -ForegroundColor Green "Mail Nickname: $($team.MailNickName)"
Write-Host -ForegroundColor Green "Archived: $($team.Archived)"
Write-Host -ForegroundColor Green "Visiblity: $($team.Visibility)"
Write-Host -ForegroundColor Green ""
Write-Host -ForegroundColor Green "Team User Details:"
Write-Host -ForegroundColor Green "Owners - $($ownerCount)"
Write-Host -ForegroundColor Green "Members - $($memberCount)"
Write-Host -ForegroundColor Green "Guests - $($guestCount)"
Write-Host -ForegroundColor Green "============================================================"

$Report | Export-CSV $exportLocation -NoTypeInformation -Force
Write-Host -ForegroundColor Blue "Exported report to $($exportLocation)"

Of course, programming against the API is always more powerful, but sometimes quick and easy is what is needed. 🙂

Git and Code

I think this from xkcd sums up my afternoon quite nicely. Messed up a repo, and then was trying to ‘clean up’.

A huge thank you to Lily, on the team, for working with me to cleaning up my mess, and helping me show some of the ropes.

I know there are quite a few tutorials our there a couple of these that I found including one from Lily.

So go ahead, and setup a experiment repo and don’t be afraid to play and break things.

Maybe this needs to be updated to reflect Git, from REST. 🙂

Deleting Windows run history

If you have butter fingers like me, and over time end up with a lot of old commands with typos in your Windows run box that get annoying – deleting them is a simple. All you need to do it remove the following registry key.


Now every time one plays with regedit, it can be dangerous – you can also save this commend as a .cmd file, and then run it with admin privileges – essentially does the same thing.

reg delete "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU" /f

You can also download the same thing from here.

Docker / Docker Compose on a Pi

Been playing with a few things at home, and as part of that was trying to get Docker and Docker Compose running on a Raspberry Pi. Docker Compose if you aren’t familiar with, allows one to run multi-container apps, and is very handy when building multi-tier layered applications – which are quite common.

I was running it docker on my (Synology) NAS, but a recent update from them broke docker – specifically environment variables. That in turn broke the ability to run Docker Compose, and of course a bunch of stuff; and the opportunity to experiment.

First, we need to install docker – which these days is quite simple. You need the ability to ssh into the pi (or if you are connected to a display, then via a terminal prompt). And in some cases if things fail then you might need to run them as root (via sudo). To install docker, run the following:

curl -sSL | sh

And once you are done installing docker, then test it by running the classic hello world image. To so that you run the following command – this will get the Hello World image, and once run will automatically remove it (which is because of the –rm option)

docker run --rm hello-world

If everything is installed OK, then you should see a output that looks something like this the shown below. And this is good – means everything is up and running as expected.

pi@pi-server2:~ $ docker run --rm hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
c1eda109e4da: Pull complete
Digest: sha256:b8ba256769a0ac28dd126d584e0a2011cd2877f3f76e093a7ae560f2a5301c00
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:

For more examples and ideas, visit:

To make like more simple, you should add the user you are logged in as to the ‘docker’ group. In my case it is the default ‘pi’ user, so that command would look like this. And for this to take effect, you would need to logout – I just reboot the machine – old habits. 🙂

sudo usermod -aG docker pi

OK, now that docker is installed, lets get to docker-compose. For that we first install pip, and use that to install docker-compose. And don’t forget the apt-get update in the end.

curl -o && sudo python3
sudo pip3 install docker-compose
sudo apt-get update

Now before anything else, lets try and make sure all dependencies are there. Create a file called ‘docker-compose.yml’ with the following. You can put this file anywhere, but I like to create a separate folder and save it in that.

In this example I expose port 6666 to the host which is mapped to port 8000 internally on the image. If your port 6666 is taken you can choose another port – it doesn’t matter. Spacing and indent, do matter in a yml file, so you would want to pay extra attention to that.

version: '3'
      - 6666:8000
    image: python:3.7-alpine
    command: "python -m http.server 8000"

Once the file is saved you run it with the following command. The image handles you would see are very likely going to be different and that is OK.

pi@pi-server2:~/docker/docker-test $ docker-compose up
Creating network "docker-test_default" with the default driver
Pulling webapp (python:3.7-alpine)...
3.7-alpine: Pulling from library/python
33b18ff7f9b7: Pull complete
0c1f90421c3a: Pull complete
91543a0ba590: Pull complete
913b1310b79e: Pull complete
6b545e90ee55: Pull complete                                                                                             Digest: sha256:9363cb46e52894a22ba87ebec0845d30f4c27efd6b907705ba9a27192b45e797
Status: Downloaded newer image for python:3.7-alpine
Creating docker-test_webapp_1 ... done                                                                                  Attaching to docker-test_webapp_1

At this point, the image is running in attached mode and it seems like it is waiting, when in reality it is running. If you open another ssh terminal and type in the following command – change the port to whatever you used earlier in the yml file.

pi@pi-server2:~ $ curl -iv

And if everything is working then you will see a output something like this. And if you see towards the top you got a HTTP 200 – that is all that mattes in this case.

* Expire in 0 ms for 6 (transfer 0x1b097c0)
*   Trying
* Expire in 200 ms for 4 (transfer 0x1b097c0)
* Connected to ( port 6666 (#0)
> GET / HTTP/1.1
> Host:
> User-Agent: curl/7.64.0
> Accept: */*
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.7.4
Server: SimpleHTTP/0.6 Python/3.7.4
< Date: Thu, 26 Sep 2019 22:10:28 GMT
Date: Thu, 26 Sep 2019 22:10:28 GMT
< Content-type: text/html; charset=utf-8
Content-type: text/html; charset=utf-8
< Content-Length: 915
Content-Length: 915

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
<h1>Directory listing for /</h1>
<li><a href=".dockerenv">.dockerenv</a></li>
<li><a href="bin/">bin/</a></li>
<li><a href="dev/">dev/</a></li>
<li><a href="etc/">etc/</a></li>
<li><a href="home/">home/</a></li>
<li><a href="lib/">lib/</a></li>
<li><a href="media/">media/</a></li>
<li><a href="mnt/">mnt/</a></li>
<li><a href="opt/">opt/</a></li>
<li><a href="proc/">proc/</a></li>
<li><a href="root/">root/</a></li>
<li><a href="run/">run/</a></li>
<li><a href="sbin/">sbin/</a></li>
<li><a href="srv/">srv/</a></li>
<li><a href="sys/">sys/</a></li>
<li><a href="tmp/">tmp/</a></li>
<li><a href="usr/">usr/</a></li>
<li><a href="var/">var/</a></li>
* Closing connection 0

You can go back to the first ssh session and hit Ctrl + C to shutdown the image. Once you do that you will see something like:

^CGracefully stopping... (press Ctrl+C again to force)
Stopping docker-test_webapp_1 ... done                                                                                  pi@pi-server2:~/docker/docker-test $

Now you know docker-compose and all the dependencies are installed. Next I would want docker to auto start whenever the pi boots up, and for that we will use the following two commands.

sudo systemctl enable docker
sudo systemctl start docker

And that should be it. If you are running low on space you might want to clean up the images we downloaded in testing this installation.

Tesla API v3.9.1

Haven’t had time until now to explore on what is new as Tesla continues to push updates. The latest version as of this post is v3.9.1 which is what there I decompiled and when compared to the earlier version (I had posted (v3.8.2), there three new REST API’s outlined below.

Service data from the car – not sure what exactly does this will. Need to try it.

    "TYPE": "GET",
    "URI": "api/1/vehicles/{vehicle_id}/service_data",
    "AUTH": true

Now, when I call that, I get a 200OK response (see below), so it is accepting the request, and that includes the bearer code in the header as expected. I don’t see anything interesting back, but that could be because my car is not in service. Maybe someone who has their vehicle in the service center can try and validate this.

    "response": {}

The next new API is a POST, for reports; and calling this just sends a 200OK back, but I don’t know what it is for. It seems very similar to the SEND_LOG method.

    "TYPE": "POST",
    "URI": "api/1/reports",
    "AUTH": true

The next two set of APIs seem quite interesting and related t AutoPilot upgrade. It might be that these could be in app purchases – checking the eligibility, and then allowing one to purchase.

    "TYPE": "GET",
    "URI": "api/1/vehicles/{vehicle_id}/eligible_upgrades",
    "AUTH": true
    "TYPE": "GET",
    "URI": "api/1/vehicles/{vehicle_id}/purchase_url",
    "AUTH": true

When I try and call the Purchase_URL, I get a HTTP 400, and seems like I am missing some parameters – other than the headers.

    "error": "bad_request",
    "error_description": "The data given to this server does not meet our criteria."

And calling the eligible_upgrades I get a ‘false’. Now I already have AutoPilot, so this might make sense. And given this seems to be a key-value pair, I am guessing there will be other things that Tesla would add over time to up-sell.

    "autopilot": false

The final new API is related to energy sites, and something I of course don’t have or have an interest, but sharing here if someone does care. 🙂

    "TYPE": "GET",
    "URI": "api/1/energy_sites/{site_id}/calendar_history",
    "AUTH": true

I am not publishing the full API here as there aren’t significant changes. You of course can see the older post which has the details.

npm install blues – npm ERR! Error: Method Not Allowed

This is a output of a few frustrating hours (spanning over a few days – as and when I can get time), and finally got it fixed and working. Hopefully it might help someone who is also dealing with npm blues.

When NodeJS and npm works, its awesome. But when it borks, it is worst than my code or so it seems :).

Been playing with a few things and wanting to get a dashboard going with Grafana (and InfluxBD as a time-series DB). But some of the installation was failing and for the life of me, could not figure out why and how. Clean image install and downgrading to the previous stable version also didn’t help.

One example of npm failing miserably was the “Error: Method not Allowed” which is not very helpful. Here is an example of what I was seeing:

root@pi-server:/var/lib/grafana/plugins/grafana-trackmap-panel# npm install
(node:4538) [DEP0022] DeprecationWarning: os.tmpDir() is deprecated. Use os.tmpdir() instead.
npm ERR! Error: Method Not Allowed
npm ERR!     at errorResponse (/usr/share/npm/lib/cache/add-named.js:260:10)
npm ERR!     at /usr/share/npm/lib/cache/add-named.js:203:12
npm ERR!     at saved (/usr/share/npm/node_modules/npm-registry-client/lib/get.js:167:7)
npm ERR!     at FSReqWrap.oncomplete (fs.js:135:15)
npm ERR! If you need help, you may report this *entire* log,
npm ERR! including the npm and node versions, at:
npm ERR!     <>

npm ERR! System Linux 4.19.57-v7+
npm ERR! command "/usr/bin/node" "/usr/bin/npm" "install"
npm ERR! cwd /var/lib/grafana/plugins/grafana-trackmap-panel
npm ERR! node -v v8.11.1
npm ERR! npm -v 1.4.21
npm ERR! code E405
npm ERR!
npm ERR! Additional logging details can be found in:
npm ERR!     /var/lib/grafana/plugins/grafana-trackmap-panel/npm-debug.log
npm ERR! not ok code 0

Again, like I said not very helpful. But I finally got to be able to fix it and move on. And here is what worked for me, and it seems like in the OS image, there was a corrupted files, at some level. In most cases you need root access.

Step 1: – Remove and clean up NodeJS.

sudo apt-get remove nodejs nodejs-legacy nodered

Step 2: Get the latest stable source.

curl -sL$NODE_STABLE_BRANCH | sudo -E bash -
sudo apt-get install -y nodejs
npm install -g npm@latest\

I also noticed sometimes the commands above don’t work. If that is the case then then try the following, to get the latest.

curl -sL | sudo -E bash -
sudo apt-get install -y nodejs
npm install -g npm@latest

And based on your dependencies, v9 might not work and you need v8 then you change the first line as following Or for the latest:

curl -sL | sudo -E bash -
sudo apt-get install -y nodejs
npm install -g npm@latest

And finally in the end install and start.

npm install && npm start

And if you do need to check for the update and get the latest, then try:

sudo npm install -g npm@latest