Posts tagged powershell


Tutorial: PowerShell Modules in FSharp

:: dotnet, fsharp, powershell, programming language, tutorial

By: Maciej Barć

F# is a amazing functional language bridging C#/.NET ecosystem with OCaml and interactive programming. It has recently became by favorite language to create personal projects in. It both has additional security of strong typing, unlike Python or Ruby while keeping the functional and interactive properties of weak-typed languages. F# is truly a engineering marvel.

PowerShell 7 is the Open-Source implementation of the old Windows PowerShell that is also cross-platform and can be used for scripting and automation. It’s Object-orientation makes an amazing extension capability compared to Bash.

You can use the F# language to create PowerShell modules. Normally PS Modules are written in C# but since the interop between .NET languages is insanely fluent one can just swap C# for F#.

Creating new project

mkdir -p ~/source/temporary/powershell-modules/fs-example
cd ~/source/temporary/powershell-modules/fs-example

Dotnet ClassLib

Init new F# Class project:

dotnet new classlib --language "F#"

Dependencies

Add dependency on Powershell interaction library:

dotnet add package PowerShellStandard.Library --version 5.1.1

F# interaction with PowerShell

Let’s rewrite Library.fs to contain:

namespace FsExample.PowerShell

open System.Management.Automation

[<Cmdlet(VerbsCommon.Format, "FsExample")>]
type FsExample() =
    inherit PSCmdlet()

    [<Parameter>]
    [<ValidateNotNullOrEmpty>]
    member val Name: string = "Me" with get, set

    override this.BeginProcessing() =
        printfn "Hello %s" this.Name

Then, change RootNamespace to the main module name, that is FsExample.PowerShell.

We need to copy all assemblies to output. Add <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>:

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

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <RootNamespace>FsExample.PowerShell</RootNamespace>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>

    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
  </PropertyGroup>

  <!-- Snip ... -->

</Project>

PowerShell library

Create PSModuleAssets directory:

mkdir -p PSModuleAssets
cd PSModuleAssets

The main module file will in turn load the DLL compiled form above F# code.

Create main module file - fs-example.psm1:

#!/usr/bin/env -S pwsh -NoLogo -NoProfile -NonInteractive

Import-Module "$PSScriptRoot/fs-example.dll"

We then need to create the main manifest file fs-example.psd1.

  • Path - relative path to output the manifest,
  • RootModule - relative module path to load on module import,
  • ModuleVersion, Description, Author and Copyright are some of standard metadata fields,
  • AliasesToExport, CmdletsToExport, DscResourcesToExport, FunctionsToExport and VariablesToExport are either globs or lists that tell what functions will be available on module load, for simplicity we will just specify a glob expression '*'.
New-ModuleManifest -Path ./fs-example.psd1 -RootModule ./fs-example.psm1 `
    -ModuleVersion "1.0.0" `
    -Description "FSharp example module" `
    -Author "Me" -Copyright "Copyright (c) 2025, Me" `
    -AliasesToExport '*' -CmdletsToExport '*' `
    -DscResourcesToExport '*' -FunctionsToExport '*' -VariablesToExport '*'

Copying PowerShell assets

Following ItemGroup will copy all files from PSModuleAssets to the output directory:

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

  <!-- Snip ... -->

  <ItemGroup>
    <None Include="PSModuleAssets/*.*">
      <Link>%(Filename)%(Extension)</Link>
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>

  <!-- Snip ... -->

</Project>

Building the F# module

Clean up the old builds:

rm -fr ./psrepository
rm -fr ./out-module/fs-example

Dotnet-Build

And then build the module:

dotnet restore
dotnet build --configuration Release --output ./out-module/fs-example

Language locality bug

You have to use the en-US locale when publishing PowerShell modules.

$env:LANG = "en_US.UTF-8"
$env:LANGUAGE = "en_US.UTF-8"
$env:LC_MESSAGES = "en_US.utf8"

Installation

Remove old registered PowerShell repository:

try { Unregister-PSRepository -Name fs-example } catch { }

Register-PSRepository and Publish-Module

This quite complicated script will:

  • set up a PowerShell repository in current location,
  • register the PowerShell repository,
  • publish the module into the PowerShell repository,
  • install that published module from the PowerShell repository.
$repo = "fs-example"
$repoPath = "$pwd/psrepository/fs-example"

New-Item -ItemType Directory -Path $repoPath | Out-Null
Register-PSRepository -InstallationPolicy Trusted -Name $repo -SourceLocation $repoPath

Publish-Module -Verbose -Path $pwd/out-module/fs-example -Repository $repo
Install-Module -Scope CurrentUser -Force -Verbose -Name fs-example -Repository $repo

Removal

Uninstall-Module

Just call Uninstall-Module to remove any PowerShell module.

Uninstall-Module -Name fs-example

Usage in the REPL

Import-Module

Import it with Import-Module.

Import-Module -Verbose fs-example -Force

Finally - use Format-FsExample

Remember the above F# function that was defined in the class?

[<Cmdlet(VerbsCommon.Format, "FsExample")>]
type FsExample()

It will be now available under the name composed of the “common verb” and a secondary part, so Format-FsExample.

Try it:

Format-FsExample -Name Maciej

.NET in Gentoo in 2023

:: dotnet, gentoo, packaging, portage, powershell

By: Maciej Barć

.NET ecosystem in Gentoo in year 2023

The Gentoo Dotnet project introduced better support for building .NET-based software using the nuget, dotnet-pkg-base and dotnet-pkg eclasses. This opened new opportunities of bringing new packages depending on .NET ecosystem to the official Gentoo ebuild repository and helping developers that use dotnet-sdk on Gentoo.

New software requiring .NET is constantly being added to the main Gentoo tree, among others that is:

  • PowerShell for Linux,
  • Denaro — finance application,
  • Ryujinx — NS emulator,
  • OpenRA — RTS engine for Command & Conquer, Red Alert and Dune2k,
  • Pinta — graphics program,
  • Pablodraw — Ansi, Ascii and RIPscrip art editor,
  • Dafny — verification-aware programming language
  • many packages aimed straight at developing .NET projects.

Dotnet project is also looking for new maintainers and users who are willing to help out here and there. Current state of .NET in Gentoo is very good but we can still do a lot better.

Special thanks to people who helped out

Installing PowerShell modules via Portage

:: dotnet, gentoo, packaging, portage, powershell

By: Maciej Barć

Building PowerShell

As a part of my work of modernizing the way .NET SDK packages are distributed in Gentoo I delved into packaging a from-source build of PowerShell for Gentoo using the dotnet-pkg eclass.

Packaging pwsh was a little tricky but I got a lot of help from reading the Alpine Linux’s APKBUILD. I had to generate special C# code bindings with ResGen and repackage the PowerShell tarball. Other than this trick, restoring and building PowerShell was pretty straight forward with the NuGet package management support from the dotnet-pkg.eclass.

Alternatively if you do not want to build PowerShell you can install the binary package, I have in plans to keep that package around even after we get the non-binary app-shells/pwsh into the official Gentoo ebuild repository.

Why install modules via Portage?

But why stop on PowerShell when we can also package multiple PS modules?

Installing modules via Portage has many benefits:

  • better version control,
  • more control over global install,
  • no need to enable PS Gallery,
  • sandboxed builds,
  • using system .NET runtime.

Merging the modules

PowerShell’s method of finding modules is at follows: check paths from the PSModulePath environment variable for directories containing valid .psd1 files which define the PS modules.

By default pwsh tries to find modules in paths:

  • user’s modules directory — ~/.local/share/powershell/Modules
  • system modules directory in /usr/local/usr/local/share/powershell/Modules
  • Modules directory inside the pwsh home — for example /usr/share/pwsh-7.3/Modules

Because we do not want to touch either /usr/local nor pwsh home, we embed a special environment variable inside the pwsh launcher script to extend the path where pwsh looks for PS modules. The new module directory is located at /usr/share/GentooPowerShell/Modules.

dotnet-pkg-utils_append_launchervar \
    'PSModulePath="${PSModulePath}:/usr/share/GentooPowerShell/Modules:"'

So every PowerShell module will install it’s files inside /usr/share/GentooPowerShell/Modules.

To follow PS module location convention we add to that path a segment for the real module name and a segment for module version. This also enables us to have proper multi-slotting because most of the time the modules will not block installing other versions.

Take a look at this example from the app-pwsh/posh-dotnet–1.2.3 ebuild:

src_install() {
    insinto /usr/share/GentooPowerShell/Modules/${PN}/${PV}
    doins ${PN}.psd1 ${PN}.psm1

    einstalldocs
}

And that is it. Some packages do not even need to be compiled, they just need files placed into specific location. But when compilation of C# code is needed we have dotnet-pkg to help.