This article will give you step-by-step instructions to author PowerShell Cmdlets and modules in F#.
PowerShell Cmdlets are an awesome way to write command line tools, especially within Microsoft’s .NET ecosystem. Generally, developing PSModules (the established means of packaging related PowerShell functionalities) is presented as a task that can either be done in C# or the PowerShell scripting language. However, a PowerShell binary module is just a .NET assembly, and F# also compiles to a .NET assembly just like C#. So how do we do it?
Well, before we begin I should note this tutorial is meant to be accessible to any platform with PowerShell Core, and the .NET Core SDK installed. Since both of those tools work on most platforms, this tutorial applies to development on Windows, macOS, and Linux (I’ve tested on both macOS and Windows).
Here are the minimum commands to go from a fresh PowerShell instance to building and running a fully scaffolded .NET Core project for developing your own PSModules and Cmdlets in only F# code. The lines of the following code block are formatted to be copypastable into a PowerShell command line (if that’s what you want to do). I recommend you “Copy-and-Paste” along in the fully annotated version of these commands in the next section.
Assuming you have a PowerShell session sitting in a directory that you would like to put your project folder in— we can start by creating a Solution to hold our PSModule library, and then create and add an F# classlib project to the solution. We accomplish this with the wonderful
dotnet command provided by the .NET SDK.
# Create a new solution
dotnet new sln -o DotnetCoreProj
# Add a FSharp class library to that solution
dotnet new classlib -lang 'F#' -o src/MyPSModule
dotnet sln add src/MyPSModule/MyPSModule.fsproj
You will see that several files and directories were automatically created after running the
dotnet new command. The file we will focus on later is
Library.fs. For now we will focus on finishing setting up the F# project so that we can actually build a PSModule.
dotnet add package PowerShellStandard.Library
Here we add the PowerShellStandard.Library package reference to the new F# project. This references the necessary assemblies to build and run PSModules. And that’s all you need to do! The next step is adding our source code. Remember
Library.fs? Replace its contents with the following:
This source code is super cool and must be discussed later. For now we will finish out building and running the Cmdlet.
You build the project with
dotnet build, which will create a
src/MyPSModule/bin/Debug/netstandard2.0/, however, importing this may not work! This is because an F# assembly needs to reference
FSharp.Core.dll in order to work. Thankfully,
dotnet publishwill include this necessary assembly. All together now:
dotnet publish# Lets RUN
Get-Foo -Name Bar
Foo is Bar
NOTE: after running
Import-Moduleon the assembly, you may find it difficult to run
dotnet buildagain. Depending on your environment, replacing the assembly with the newly compiled assembly may not be possible because the file might be locked by the PowerShell instance that has loaded it.
The Code (again)
The best way to understand the code in
Library.fs is to see it translated into C#. I give you
Assuming some knowledge of either C# or F#, you can see that this example is almost a direct translation. The namespace and import syntax of each language maps directly aside from being somewhat simpler in F#.
Besides that, a
type in F# can map directly to a C#
class and the
[Foo] decorator syntax in C# just gets additional alligator brackets
[<Foo>] in F#. This is incredibly useful when writing Cmdlets, as it is used as a means of defining Cmdlet parameter attributes as well as writing the Cmdlet class definition.
The first piece that is likely to be the least familiar to most is the use of
x. on lines 12 and 13. This is a feature of F# that allows a "member function" to specify a unique name of the underlying object on a per-function basis, these are called Self Identifiers. F# can also make use of auto properties just like C# by using the
with keyword, which, in my opinion, is somewhat unclear but is widely used in C#.
It’s worth noting that a direct translation such as this will inherently introduce an instance of an object with mutable state, and is therefore not purely functional, but F# was never a purely functional language to begin with. Even so, if you treat a PSCmdlet object as just the entrypoint to your processing, you should be able to carry on as if it was purely functional.
This should provide enough to get things started for writing new PSModules and PSCmdlets in only F#, which I think is super cool. Functional programming rocks!