using Programming;

A Blog about some of the intrinsics related to programming and how one can get the best out of various languages.

Running F# code in PowerShell

Impromptu Blog Post: Loading F# Into PowerShell

My friend Chris asked on Twitter, if it was possible to load an F# module to PowerShell. For anyone who's heavily familiar with PowerShell, we know that it's very possible to load a .NET assembly to PowerShell, and run methods and operations in it.

To run an F# module in PowerShell, we simply need to add an extra step: load FSharp.Core.dll before we try to call our method.

To test this, I built a quick F# project: PowerShell Test Library, and added the following code:

namespace PowerShell_Test_Library

type TestClass() = 
    member this.run () = printfn "This is an F# function"
    static member runStatic () = printfn "This is a static F# function"

Basic, right? This is just demonstrative, but let's take a peek and see how we can load it. The first step to loading a .NET library into PowerShell is to call System.Reflection.Assembly.LoadFile, which is done as follows:

[System.Reflection.Assembly]::LoadFile("C:\Users\ebrown\Documents\Visual Studio 2017\Projects\FSharp Tests\PowerShell Test Library\bin\Debug\PowerShell_Test_Library.dll")

Yes, you need the full path when using it like this. Next, we load FSharp.Core.dll:

[System.Reflection.Assembly]::LoadFile("C:\Users\ebrown\Documents\Visual Studio 2017\Projects\FSharp Tests\PowerShell Test Library\bin\Debug\FSharp.Core.dll")

We can call our methods as follows:

(New-Object PowerShell_Test_Library.TestClass).run()
[PowerShell_Test_Library.TestClass]::runStatic()

So, altogether, our PowerShell is:

[System.Reflection.Assembly]::LoadFile("C:\Users\ebrown\Documents\Visual Studio 2017\Projects\FSharp Tests\PowerShell Test Library\bin\Debug\PowerShell_Test_Library.dll")
[System.Reflection.Assembly]::LoadFile("C:\Users\ebrown\Documents\Visual Studio 2017\Projects\FSharp Tests\PowerShell Test Library\bin\Debug\FSharp.Core.dll")
(New-Object PowerShell_Test_Library.TestClass).run()
[PowerShell_Test_Library.TestClass]::runStatic()

If we run this, we get the following:

PS C:\Users\ebrown\Desktop> [System.Reflection.Assembly]::LoadFile("C:\Users\ebrown\Documents\Visual Studio 2017\Projects\FSharp Tests\PowerShell Test Library\bin\Debug\PowerShell_Test_Library.dll")

GAC    Version        Location
---    -------        --------
False  v4.0.30319     C:\Users\ebrown\Documents\Visual Studio 2017\Projects\FSharp Tests\PowerShell Test Library\bin...


PS C:\Users\ebrown\Desktop> [System.Reflection.Assembly]::LoadFile("C:\Users\ebrown\Documents\Visual Studio 2017\Projects\FSharp Tests\PowerShell Test Library\bin\Debug\FSharp.Core.dll")

GAC    Version        Location
---    -------        --------
False  v4.0.30319     C:\Users\ebrown\Documents\Visual Studio 2017\Projects\FSharp Tests\PowerShell Test Library\bin...


PS C:\Users\ebrown\Desktop> (New-Object PowerShell_Test_Library.TestClass).run()
This is an F# function
PS C:\Users\ebrown\Desktop> [PowerShell_Test_Library.TestClass]::runStatic()
This is a static F# function
PS C:\Users\ebrown\Desktop>

And it's that easy.

For anyone familiar with PowerShell, if your ExecutionPolicy does not permit running a script, you can use the following function to run it instead:

function Run-Script ([string]$script)
{
    $policy = Get-ExecutionPolicy
    Set-ExecutionPolicy -Force -Scope CurrentUser -ExecutionPolicy Bypass
    & ".\$script"
    Set-ExecutionPolicy -Force -Scope CurrentUser -ExecutionPolicy $policy
}

Drop that in your PowerShell console, and then call Run-Script ScriptFile.ps1, which for me is Run-Script Script.ps1. This will set and restore your ExecutionPolicy to run the script:

PS C:\Users\ebrown\Documents\Visual Studio 2017\Projects\FSharp Tests\PowerShell Test Library\bin\Debug> function Run-Script ([string]$script)
>> {
>> $policy = Get-ExecutionPolicy
>> Set-ExecutionPolicy -Force -Scope CurrentUser -ExecutionPolicy Bypass
>> & ".\$script"
>> Set-ExecutionPolicy -Force -Scope CurrentUser -ExecutionPolicy $policy
>> }
PS C:\Users\ebrown\Documents\Visual Studio 2017\Projects\FSharp Tests\PowerShell Test Library\bin\Debug> Run-Script Script.ps1

GAC    Version        Location
---    -------        --------
False  v4.0.30319     C:\Users\ebrown\Documents\Visual Studio 2017\Projects\FSharp Tests\PowerShell Test Library\bin...
False  v4.0.30319     C:\Users\ebrown\Documents\Visual Studio 2017\Projects\FSharp Tests\PowerShell Test Library\bin...
This is an F# function
This is a static F# function


PS C:\Users\ebrown\Documents\Visual Studio 2017\Projects\FSharp Tests\PowerShell Test Library\bin\Debug>

And this concludes our main lesson for today. :)


Make it a little better

Of course, we can make this just a little better.

First, we don't want to have to manually specify the path. So we'll get the current path:

$path = (Get-Item -Path "." -Verbose).FullName

Second, we don't really need to see the GAC lines from the libraries being added, we don't really care about them. The easiest way to do this is to redirect the output to $null, via > $null:

[System.Reflection.Assembly]::LoadFile("$path\PowerShell_Test_Library.dll") > $null
[System.Reflection.Assembly]::LoadFile("$path\FSharp.Core.dll") > $null

This will make our new script less verbose.

PS C:\Users\ebrown\Documents\Visual Studio 2017\Projects\FSharp Tests\PowerShell Test Library\bin\Debug> Run-Script Script.ps1
This is an F# function
This is a static F# function
PS C:\Users\ebrown\Documents\Visual Studio 2017\Projects\FSharp Tests\PowerShell Test Library\bin\Debug>

It also builds a reusable framework for future scripts: we can replace the first LoadFile with our apporpriate file, so we may actually want to abstract that:

function Init-Script ($dll)
{
    $path = (Get-Item -Path "." -Verbose).FullName
    [System.Reflection.Assembly]::LoadFile("$path\FSharp.Core.dll")
    [System.Reflection.Assembly]::LoadFile("$path\$dll.dll")
}

Then the actual script code is:

Init-Script "PowerShell_Test_Library" > $null
(New-Object PowerShell_Test_Library.TestClass).run()
[PowerShell_Test_Library.TestClass]::runStatic()

And life is much better.

Loading