Using Visual Studio 2022 Hot Reload for Developing Binary PowerShell Cmdlets

Image Description

Daily PowerShell #30

Daily PowerShell dotnet

November 15, 2021

In this post, we’ll look at how to use Hot Reload in Visual Studio 2022 to develop binary PowerShell cmdlets.

What is Hot Reload?

Hot Reload is functionality of Visual Studio 2022 and .NET that allow you to make changes to a running .NET application and the changes will be automatically recompiled without having to restart the application. You can trigger a Hot Reload or enable it based on file changes by clicking the Hot Reload button in the toolbar.

Visual Studio Hot Reload

Creating a Console Host Application

The first step to enable development of binary modules is to create a new console host application to run our PowerShell module.

Click File \ New Project and select the C# Console application with the .NET 6.0 framework. Next, we’ll need to add the Microsoft.PowerShell.SDK to NuGet package. Right click on your project and click Manage NuGet Packages.

Search for the Microsoft.PowerShell.SDK package and install it to your project. The resulting .csproj file should look like this.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.PowerShell.SDK" Version="7.2.0" />
  </ItemGroup>
</Project>

Next, we need to create a console loop that can run our PowerShell cmdlets. In Program.cs, you can include the following processing loop to execute commands entered into the console. Typing exit will terminate the console loop.

using System.Management.Automation;

do
{
    var commandLine = Console.ReadLine();
    if (commandLine == "exit")
    {
        break;
    }

    using var powerShell = PowerShell.Create();
    powerShell.AddStatement().AddScript(commandLine);
    powerShell.AddCommand("Out-String");

    try
    {
        var item = powerShell.Invoke<string>().FirstOrDefault();
        Console.WriteLine(item);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }

} while (true);

Creating a PowerShell Binary Module

Next, we can create a binary module by click File \ New Project and selecting a Class Library project. You will want to target .NET Standard 2.0 so that it works in both Windows PowerShell and PowerShell 7+.

Once you have created the class library, you will need to add the PowerShellStandard.Library NuGet package. Repeat the steps of managing the NuGet Packages for your Class Library project and install it into your project.

My class library .csproj looks like this.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="PowerShellStandard.Library" Version="5.1.0" />
  </ItemGroup>

</Project>

Now that we have the library added, we can reference it in our Console Application. Right click on the Dependencies node of the Console project and click Add Project Reference. This will show the Reference Manager where we can add a reference to the project.

Once updated, the console application will include a reference to the class library.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.PowerShell.SDK" Version="7.2.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj" />
  </ItemGroup>

</Project>

Finally, we can create a cmdlet within the binary module project. The Write-Hello cmdlet just prints the text that is supplied to it.

using System.Management.Automation;

namespace ClassLibrary1
{
    [Cmdlet("Write", "Hello")]
    public class WriteHelloCommand : PSCmdlet
    {
        [Parameter(Position=0)]
        public string Text { get; set; }

        protected override void ProcessRecord()
        {
            WriteObject(Text);
        }
    }
}

Calling the Binary Module from the Console Application

Now that we have a console application and a binary module, we can import our module and execute the Write-Hello cmdlet.

We can update the console application to import the module automatically.

using System.Management.Automation;

do
{
    var commandLine = Console.ReadLine();
    if (commandLine == "exit")
    {
        break;
    }

    using var powerShell = PowerShell.Create();
    powerShell.AddCommand("Import-Module").AddParameter("Name", Path.Combine(Environment.CurrentDirectory, "ClassLibrary1.dll"));
    powerShell.AddStatement().AddScript(commandLine);
    powerShell.AddCommand("Out-String");

    try
    {
        var item = powerShell.Invoke<string>().FirstOrDefault();
        Console.WriteLine(item);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }

} while (true);

Now, start the console application by pressing F5 or clicking the Start button on the toolbar. You’ll be able to execute the cmdlet by typing it into the console application.

Write-Hello -Text 'Hello'
Hello

Perform a Hot Reload

With the application running, you can now make changes to cmdlet and see the result right away. In my environment, I have Hot Reload enabled on file save. Update the cmdlet to include some additional text within the WriteObject call.

using System.Management.Automation;

namespace ClassLibrary1
{
    [Cmdlet("Write", "Hello")]
    public class WriteHelloCommand : PSCmdlet
    {
        [Parameter(Position=0)]
        public string Name { get; set; }

        protected override void ProcessRecord()
        {
            WriteObject(Name + ", World!");
        }
    }
}

Once the Hot Reload completes, go back to the console application and run the cmdlet again.

Write-Hello -Text 'Hello'
Hello, World!

Limitations of Hot Reload

Not all types of edits are supported by Hot Reload. For example, modifying or adding attributes is not supported. This means you’ll have to restart your application to add parameters to your cmdlets.

For a full list of the limitations of Hot Reload, you can see this documentation.