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.