Using the Polyglot extension from VSCode for creating Jupyter notebooks containing PowerShell code and how to use the PowerShellNotebook module

I did a small demo session at Experts Live NL this week about this topic. In this blog post, I aim to demonstrate the capabilities of the Polyglot extension, explain its functionality, which is easier compared to the original method of creating Jupyter notebooks, which I covered in 2022.

What is Jupyter?

“The Jupyter Notebook is an open-source web application that you can use to create and share documents that contain live code, equations, visualizations, and text. The people maintain Jupyter Notebook at Project Jupyter.

Jupyter Notebooks are a spin-off project from the IPython project, which used to have an IPython Notebook project. Jupyter comes from the core supported programming languages it supports: Julia, Python, and R. Jupyter ships with the IPython kernel, allowing you to write your programs in Python. Still, there are currently over 100 other kernels that you can also use.”

Source: https://realpython.com/jupyter-notebook-introduction/, and for more information and how to use it, please visit my previous blog post about it here from 2022: https://powershellisfun.com/2022/08/04/jupyter-notebooks-in-vscode-with-powershell-support/

What is the Polyglot extension from VSCode?

“The Polyglot Notebooks extension, powered by .NET Interactive, brings support for multi-language notebooks to Visual Studio Code. Classic notebook software typically supports notebooks that use only one language at a time. With Polyglot Notebooks, features such as completions, documentation, syntax highlighting, and diagnostics are available for many languages in one notebook. In addition, different cells in the same notebook can run in separate processes or on different machines, allowing a notebook to span local and cloud environments in one combined workflow.

Read more in our FAQ.”

It’s an extension created by Microsoft to allow you to create, edit, and view Jupyter notebooks in Visual Studio Code. But they made it a lot easier, because you can start using PowerShell as a kernel straight after installing the .NET SDK v9.0 (Install that yourself) and .NET Interactive (VSCode will prompt you for it automatically)

But what does it do?

A Jupyter notebook (A file with the “.ipynb” extension) allows you to create documentation with Markdown support and the option to add a Code block. This is something that you could already do with Markdown, but within a Jupyter notebook, you can run that code straight away in that same .ipynb file! Below is a screenshot from one of the examples that I used in my session at Experts Live NL:

Below Step 1, you can see a code block that contains PowerShell code to check if the ExchangeOnlineManagement module is installed before proceeding with the procedure inside the Jupyter notebook. You can see the Play button, after pressing it, it will run that Code block and show you the results inside the notebook:

As you can see, it identified a few ExchangeOnlineManagement PowerShell modules on my system, and it also indicates the time it took to find them (2.9 seconds). You can do this for every Code block inside the Jupyter notebook, and it will remember all the variables and sessions, too 🙂

What’s the use case for this?

You can write documentation/instructions for your Service Desk Engineers, for example, and tell them where to find the corresponding PowerShell Scripts or add that to your documentation, requiring the Engineer to copy/paste that into PowerShell ISE/Visual Studio Code. Using a Jupyter notebook allows the Engineer to follow instructions, run code, see the results, and continue the instructions without leaving Visual Studio Code. If they run into issues, you can see the results in the notebook and adjust your Code inside it, too.

It also allows you to set a variable in PowerShell, for example, and reuse that same variable inside a different Code block, which was written in C#, for example. More information about that here: https://github.com/dotnet/interactive/blob/main/docs/variable-sharing.md

Supported languages

Below are the supported languages inside a Polyglot notebook, features, and how to get started:

How to write a Jupyter notebook

Creating a new notebook

After installing the Extension, you can create a new, PowerShellisfun.ipynb, for example, notebook. Visual Studio Code will recognize that it’s a Jupyter notebook and will start a new one looking like this:

If not already selected (.NET Interactive), make sure that a kernel is chosen on the top right of the Notebook. You can do that by clicking on Select Kernel:

Click on “Select another Kernel…” and select .NET Interactive.

First, you can add a piece of Markdown, which is the first chapter of the Notebook, by selecting “+ Markdown”.

Add some text, # PowerShell is fun, for example, and press Escape (Or click the button)

This will result in:

Now you can add a PowerShell Code block by selecting “+ Code“.

A blinking cursor will appear, and you can add PowerShell Code, but if you look to the right, it will show csharp – C# Script. Click on that and select pwsh – PowerShell.

After selecting that, and adding Get-Process -Name Code in the Code block, for example, you can click on the Checkbox or press Escape to exit editing the Code block. (Things like auto-complete/intellisense, Ctrl+Space will work to complete Cmdlets, etc.)

If you press Play, the Code block will execute and show you all the processes that are named Code: (Visual Studio Code)

Clearing output

You can press the Clear All Outputs button to clear all the results from the Code blocks that you executed.

Viewing/browsing the chapters

Press the Outline button to show the Chapters from your notebook. (It will show it in a Tree format if you have multiple chapters with different headings)

Inspecting variables

Press Variables to show all Variables that were created by running Code blocks inside the notebook:

Sharing values with another Code block language

When inside the Polyglot Notebook Variables pane, you can click on the Arrow icon next to the Variable to share it with another Code Block language:

Select the language, csharp # Script, for example, and it will create a Code block for that:

Limitations

Unfortunately, Polyglot/Jupyter has its limitations:

  • Output can’t be displayed in an Out-GridView window or Out-ConsoleGridViewPane; scripts that use this to select items will fail
  • The PowerShell version is 1.0.0 because it runs inside the .NET Interactive Host. As a result, it may not be compatible with all modules or scripts that have version checks within them. (Note: It seems to be PowerShell v7 because the pwsh kernel is used)⁉️
  • Modern Authentication screens will not pop up; you need Device or Certificate-based login for anything Entra/365

Using the PowerShellNotebook module

You can also use the PowerShellNotebook module from Doug Finke to create a new Jupyter notebook from the PowerShell prompt. This can be used to run a PowerShell script block and document the results in a Jupyter notebook for documentation purposes.

Note: This works with the method described in the blog post article link in the What is Jupyter chapter.

Installation

You can run the following to install the PowerShellNotebook module:

Install-Module -Name PowerShellNotebook -Scope CurrentUser

This will make the following Cmdlets available for you:

Creating a new Jupyter Notebook

You can run the following command to create a new, c:\scripts\test.ipynb, notebook, for example, which contains a Title and a Code block:

New-PSNotebook -NoteBookName C:\Scripts\Test.ipynb -IncludeCodeResults {
        Add-NotebookMarkdown -markdown "# PowerShell is fun"
        Add-NotebookCode -code 'Get-Process -Name Code'
    }

After executing the command and opening the c:\scripts\test.ipynb file in VSCode, it will look like this: (It added the Code to the Code block, ran the code while saving so that you have the results inside the Notebook already)

However, this PowerShell module was created for the standard Jupyter installation; see the link to my old article from 2022 in the “What is Jupyter” chapter above, and it does work using that method. To use this, you will have to switch from the .NET Interactive kernel to the Python one in the top right corner:

After switching the kernel, you can then press Play on the Code block to rerun it.

More information on the other cmdlets

You can find more information on the GitHub page of this project from Doug Finke here, as mentioned above (https://github.com/dfinke/PowerShellNotebook).

Wrapping up

And that’s how you can use the Polyglot extension to create nice documentation containing runnable Code inside VSCode. This is what I demonstrated in the demo session at the Experts Live event on June 2nd, 2025. One of the questions from the audience was if you could add Code or Markdown using scripts to a jupyter notebook, and with the module from Doug Finke, you can 🙂 Hope you have a lovely long “Pinksteren” (Whitsun/Pentecost) weekend!

The PowerPoint presentation I used for the demo session at Experts Live

You can download/view the presentation below:

The two examples I used in the demo session at Experts Live

Please copy/paste them as an .ipynb file to use them. C:\Scripts\Exampl1.ipynb, for instance.

Example 1: Update Send As Permissions📩

{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "95733f44",
   "metadata": {},
   "source": [
    "# Update Send As permissions\n",
    "\n",
    "This document describes how to grant a user permission to use Send As of another user's mailbox.\n",
    "\n",
    "## Step 1 - Install the ExchangeOnlineManagement module if not already installed\n",
    "\n",
    "### Check if already installed"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "aa739cb0",
   "metadata": {
    "polyglot_notebook": {
     "kernelName": "pwsh"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "#Check if ExchangeOnlineManagement module is present for current user\n",
    "Get-Module ExchangeOnlineManagement -ListAvailable"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fd5b5248",
   "metadata": {},
   "source": [
    "### Install for current user if not (When previous command above didn't return antyhing), skip Step 2 if above command returned the module"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dd469909",
   "metadata": {
    "polyglot_notebook": {
     "kernelName": "pwsh"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "#Answer the question for installing a module from an untrusted repository with Y in the prompt at the top of the screen. Rerun above command to verify if installed correctly\n",
    "Install-Module ExchangeOnlineManagement -Scope CurrentUser"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fa2ebde5",
   "metadata": {},
   "source": [
    "## Step 2 - Connect to Exchange Online"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5aeccec7",
   "metadata": {
    "polyglot_notebook": {
     "kernelName": "pwsh"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "#Connect to Exchange Online, request Exchange Admin rights first using PIM if needed. Open a browser, browse to https://microsoft.com/devicelogin, and enter the code and credentials\n",
    "Connect-ExchangeOnline -Device"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "adeb378f",
   "metadata": {},
   "source": [
    "## Step 3 - Verify names by retrieving the mailbox and usernames"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7e5cd9ff",
   "metadata": {
    "polyglot_notebook": {
     "kernelName": "pwsh"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "Get-Mailbox"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0237650d",
   "metadata": {},
   "source": [
    "## Step 4 - Update mailbox permissions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "756f948b",
   "metadata": {
    "polyglot_notebook": {
     "kernelName": "pwsh"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "#Change Mailbox to the mailbox name you want to add the Send As permission to for the user. Replace usernamehere for the user you want to add the permissions for\n",
    "#For example, Add-MailboxPermission -Identity \"Test\" -User test@domain.com -AccessRights FullAccess -InheritanceType All\n",
    "Add-MailboxPermission -Identity \"Test\" -User test@domain.com -AccessRights FullAccess -InheritanceType All"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".NET (C#)",
   "language": "C#",
   "name": ".net-csharp"
  },
  "language_info": {
   "codemirror_mode": "shell",
   "file_extension": ".ps1",
   "mimetype": "text/x-sh",
   "name": "powershell"
  },
  "polyglot_notebook": {
   "kernelInfo": {
    "defaultKernelName": "csharp",
    "items": [
     {
      "aliases": [],
      "name": "csharp"
     }
    ]
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}

Example 2: Reset MFA for a specific user🔐

{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "5a4b09ff",
   "metadata": {},
   "source": [
    "# Reset MFA for a user\n",
    "\n",
    "## Step 1 - Execute the PowerShell Code block below and enter the user you want to reset their MFA Registration for"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "polyglot_notebook": {
     "kernelName": "pwsh"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "$UserId = Read-Host \"Enter the UserPrincipalName of the user\"\n",
    "\n",
    "Function ConnectTo-MgGraph {\n",
    "    # Check if MS Graph module is installed\n",
    "    if (-not(Get-InstalledModule -Name Microsoft.Graph.Authentication, Microsoft.Graph.Users)) { \n",
    "        Write-Host \"Microsoft Graph module not found\" -ForegroundColor Black -BackgroundColor Yellow\n",
    "        $install = Read-Host \"Do you want to install the Microsoft Graph Module?\"\n",
    "\n",
    "        if ($install -match \"[yY]\") {\n",
    "            Install-Module Microsoft.Graph -Repository PSGallery -Scope CurrentUser -AllowClobber -Force\n",
    "        }\n",
    "        else {\n",
    "            Write-Host \"Microsoft Graph module is required.\" -ForegroundColor Black -BackgroundColor Yellow\n",
    "            exit\n",
    "        } \n",
    "    }\n",
    "\n",
    "    # Connect to Graph\n",
    "    Write-Host \"Connecting to Microsoft Graph\" -ForegroundColor Cyan\n",
    "    Connect-MgGraph -Scopes \"User.Read.all\", \"UserAuthenticationMethod.ReadWrite.All\" -NoWelcome -UseDeviceCode\n",
    "}\n",
    "\n",
    "function DeleteAuthMethod($uid, $method) {\n",
    "    switch ($method.AdditionalProperties['@odata.type']) {\n",
    "        '#microsoft.graph.fido2AuthenticationMethod' { \n",
    "            Write-Host 'Removing fido2AuthenticationMethod'\n",
    "            Remove-MgUserAuthenticationFido2Method -UserId $uid -Fido2AuthenticationMethodId $method.Id\n",
    "        }\n",
    "        '#microsoft.graph.emailAuthenticationMethod' { \n",
    "            Write-Host 'Removing emailAuthenticationMethod'\n",
    "            Remove-MgUserAuthenticationEmailMethod -UserId $uid -EmailAuthenticationMethodId $method.Id\n",
    "        }\n",
    "        '#microsoft.graph.microsoftAuthenticatorAuthenticationMethod' { \n",
    "            Write-Host 'Removing microsoftAuthenticatorAuthenticationMethod'\n",
    "            Remove-MgUserAuthenticationMicrosoftAuthenticatorMethod -UserId $uid -MicrosoftAuthenticatorAuthenticationMethodId $method.Id\n",
    "        }\n",
    "        '#microsoft.graph.phoneAuthenticationMethod' { \n",
    "            Write-Host 'Removing phoneAuthenticationMethod'\n",
    "            Remove-MgUserAuthenticationPhoneMethod -UserId $uid -PhoneAuthenticationMethodId $method.Id\n",
    "        }\n",
    "        '#microsoft.graph.softwareOathAuthenticationMethod' { \n",
    "            Write-Host 'Removing softwareOathAuthenticationMethod'\n",
    "            Remove-MgUserAuthenticationSoftwareOathMethod -UserId $uid -SoftwareOathAuthenticationMethodId $method.Id\n",
    "        }\n",
    "        '#microsoft.graph.temporaryAccessPassAuthenticationMethod' { \n",
    "            Write-Host 'Removing temporaryAccessPassAuthenticationMethod'\n",
    "            Remove-MgUserAuthenticationTemporaryAccessPassMethod -UserId $uid -TemporaryAccessPassAuthenticationMethodId $method.Id\n",
    "        }\n",
    "        '#microsoft.graph.windowsHelloForBusinessAuthenticationMethod' { \n",
    "            Write-Host 'Removing windowsHelloForBusinessAuthenticationMethod'\n",
    "            Remove-MgUserAuthenticationWindowsHelloForBusinessMethod -UserId $uid -WindowsHelloForBusinessAuthenticationMethodId $method.Id\n",
    "        }\n",
    "        '#microsoft.graph.passwordAuthenticationMethod' { \n",
    "            # Password cannot be removed currently\n",
    "        }        \n",
    "        Default {\n",
    "            Write-Host 'This script does not handle removing this auth method type: ' + $method.AdditionalProperties['@odata.type']\n",
    "        }\n",
    "    }\n",
    "    return $? # Return true if no error and false if there is an error\n",
    "}\n",
    "\n",
    "# Connect to MSFT Graph\n",
    "ConnectTo-MgGraph\n",
    "$methods = Get-MgUserAuthenticationMethod -UserId $userId\n",
    "\n",
    "# -1 to account for passwordAuthenticationMethod\n",
    "Write-Host \"Found $($methods.Length - 1) auth method(s) for $userId\"\n",
    "\n",
    "$defaultMethod = $null\n",
    "foreach ($authMethod in $methods) {\n",
    "    $deleted = DeleteAuthMethod -uid $userId -method $authMethod\n",
    "    if (!$deleted) {\n",
    "        # We need to use the error to identify and delete the default method.\n",
    "        $defaultMethod = $authMethod\n",
    "    }\n",
    "}\n",
    "\n",
    "# Graph API does not support reading default method of a user.\n",
    "# Plus default method can only be deleted when it is the only (last) auth method for a user.\n",
    "# We need to use the error to identify and delete the default method.\n",
    "if ($null -ne $defaultMethod) {\n",
    "    Write-Host \"Removing default auth method\"\n",
    "    $result = DeleteAuthMethod -uid $userId -method $defaultMethod\n",
    "}\n",
    "\n",
    "Write-Host \"Re-checking auth methods...\"\n",
    "$methods = Get-MgUserAuthenticationMethod -UserId $userId\n",
    "\n",
    "# -1 to account for passwordAuthenticationMethod\n",
    "Write-Host \"Found $($methods.Length - 1) auth method(s) for $userId\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "597be0b1",
   "metadata": {},
   "source": [
    "### When receiving a DeviceCodeCredential authentication error while executing script above\n",
    "\n",
    "If you receive an error stating \"\n",
    "Error: DeviceCodeCredential authentication failed: Object reference not set to an instance of an object.\n",
    "DeviceCodeCredential authentication failed: Object reference not set to an instance of an object.\"\n",
    "\n",
    "This means that the user has not active MFA method registerd, user should goto aka.ms/mfasetup to setup one if not redirected automatically when visiting portal.office.com, for example"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cb82d717",
   "metadata": {},
   "source": [
    "## Step 2 - Inform the user to login and visit aka.ms/mfasetup if not redirected automatically"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fc7ab56d",
   "metadata": {},
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".NET (C#)",
   "language": "C#",
   "name": ".net-csharp"
  },
  "language_info": {
   "codemirror_mode": "shell",
   "file_extension": ".ps1",
   "mimetype": "text/x-sh",
   "name": "powershell"
  },
  "polyglot_notebook": {
   "kernelInfo": {
    "defaultKernelName": "csharp",
    "items": [
     {
      "aliases": [],
      "name": "csharp"
     }
    ]
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.