MountAnything 0.6.0

.NET 6.0
There is a newer version of this package available.
See the version list below for details.
dotnet add package MountAnything --version 0.6.0
NuGet\Install-Package MountAnything -Version 0.6.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="MountAnything" Version="0.6.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add MountAnything --version 0.6.0
#r "nuget: MountAnything, 0.6.0"
#r directive can be used in F# Interactive, C# scripting and .NET Interactive. Copy this into the interactive tool or source code of the script to reference the package.
// Install MountAnything as a Cake Addin
#addin nuget:?package=MountAnything&version=0.6.0

// Install MountAnything as a Cake Tool
#tool nuget:?package=MountAnything&version=0.6.0

MountAnything

A framework for building powershell providers to make it easy to navigate arbitrary API's as a hierarchical virtual filesystem of objects.

Getting started

  1. Reference the MountAnything and MountAnything.Hosting.Build nuget packages in your csproj project that will contain your powershell provider.
  2. Create a class that implements the IMountAnythingProvider interface.
  3. Implement the CreateRouter method. For information on creating a router, see the Router section below.
  4. When you build your project, it will output a powershell .psd1 module file in a Module subdirectory of your projects output path (e.g. bin/Debug/net6.0/Module). You can test it out by importing that module into your powershell session and then using the New-PSDrive command to mount your provider to a drive. If you would like your provider to automatically mount a drive when the module is loaded, you can implement the optional GetDefaultDrives method in your IMountAnythingProvider implementation.

Key abstractions

There are three key abstractions that drive MountAnything. The Router, PathHandler's, and Item's:

Router

Every path in the virtual filesystem is processed by the Router to determine which PathHandler will process the command. The Router API composes a nested hierarchy of routes. Under the hood, routes are regex based, but you usually can use a more convenient extension method to avoid needing to actually deal with regex's. Here is an example of the routing api from the MountAws project:

router.MapRegex<RegionHandler>("(?<Region>[a-z0-9-]+)", region =>
{
    region.MapLiteral<EcsRootHandler>("ecs", ecs =>
    {
        ecs.MapLiteral<TaskDefinitionsHandler>("task-definitions", taskDefinitions =>
        {
            taskDefinitions.Map<TaskDefinitionHandler>();
        });
        ecs.MapLiteral<ClustersHandler>("clusters", clusters =>
        {
            clusters.Map<ClusterHandler,Cluster>(cluster =>
            {
                cluster.MapLiteral<ServicesHandler>("services", services =>
                {
                    services.Map<ServiceHandler>();
                });
            });
        });
    });
});

In the example, you can see a few different variations of Map methods used. All of them take a generic type argument that corresponds to the IPathHandler that will be invoked for matching routes. They are:

  • MapLiteral - This matches on the literal string (e.g. constant) passed into it. Only that literal string will match the route.
  • Map<THandler> - This matches any supported character (pretty much anything besides a /, which is used as the path separator) at this hierarchy level. You can optionally pass in a string as the first argument to this method if you would like to capture the value of the matched value. The captured value will be given the name that is passed as the argument. The captured value can be used for dependency injection into the PathHander of this or any child route.
  • Map<THandler,TCapture> - This is similar to the Map above, except it contains a second type parameter that is a TypedString whose value will be populated from the matched route value and can be injected into the constructor of this or any child PathHandler.
  • MapRegex - This is the lower level method that the above two methods call under the hood. Any regex is acceptable, so long as it does not contain the ^ or $ characters for declaring the beginning or end of a string. Those are implicitly added by the router as necessary. It is important to note that any regex you are adding is implicitly concatenated with the regex's built by parent and child routes when the router is matching. Named captures are allowed in the regex and those captured values can be used for dependency injection into the PathHandler of this or any child route.

PathHandler

The PathHandler is in charge of processing a command to the powershell provider. While there is an IPathHandler, it is expected that 99% of the time you will want to use the PathHandler abstract base class instead for convenience. It will automatically handle things like caching for you, which helps make things like tab completion as performant as possible.

The PathHandler base class has only two methods that you are required to implement:

  • GetItemImpl - This is called when the Get-Item command is called. It should return the IItem that corresponds to the path that this PathHandler is processing. If no item exists at this path, it should return null.
  • GetChildItemsImpl - This is called when the Get-ChildItems command. Its also used to support tab completion by default. It should return all of the child items of the item returned by the GetItemImpl method.

In addition, you can optionally override the following methods when helpful/necessary:

  • ExistsImpl - By default, existence is checked by calling GetItem and determining if it returned null or not. However, if you can provide a more performant/optimal implementation, you can override this method.
  • GetChildItems(string filter) - This method supports tab completion, as well as when the -Filter argument is used on the Get-ChildItems command. By default, the GetChildItemsImpl method is called and the filter as applied to entire set of items returned. However, if you can provide a more performant implementation that does not require fetching all items first, you are encouraged to do so by overriding this method.
  • CacheChildren - By default, the paths of the child items returned by GetChildItemsImpl are cached to help make things like tab completion faster. However, if there are potentially a very large number of child items for this handler, you may want to tell it not to do this by overriding this property and returning false.
  • GetItemCommandDefaultFreshness - This allows you to customize when the cache is used for Get-Item commands.
  • GetChildItemsCommandDefaultFreshness - This allows you to customize when the cache is used for Get-ChildItems commands.

Item

This represents the object/item that is returned to the console by Get-Item and Get-ChildItems commands. It is generally a wrapper class around an underlying object that will be sent to the console. There is a generic version of Item<T> where the type argument represents the .net type of the item that will be sent to the console. If you inherit from the non-generic Item, the underlying type will be a PSObject. Either way, all properties on the underlying type will be written to the powershell pipeline. The Item class has a couple methods that need to be implemented in the subclass to tell the powershell provider what the path of the item is:

  • ItemName - This identifies the virtual "filename" of the item. It should be something that naturally identifies the item. Prefer human friendly names if they are guaranteed to be unique.
  • IsContainer - This indicates whether this could have child items or not.

Here is an example of a simple Item implementation:

public class SecurityGroupItem : Item<SecurityGroup>
{
    public SecurityGroupItem(ItemPath parentPath, SecurityGroup securityGroup) : base(parentPath, securityGroup) {}

    public override string ItemName => UnderlyingObject.GroupId;
    
    public override bool IsContainer => false;
}

Dependency Injection

All IPathHandler instances support dependency injection, powered by Autofac. The Router provides a RegisterServices method that allows you to use Autofac's ContainerBuilder to register additional services that can be injected into your PathHandler's. Services can be registered at any point in the routing hierarchy and a registration further down in the hierarchy will override one that happens higher up. For example, take this example:

// registers the default implementation of RegionEndpoint to be us-east-1
router.RegisterServices(builder => builder.Register(_ => RegionEndpoint.UsEast1));
router.MapLiteral<RegionsHandler>("regions", regions =>
{
    regions.Map<RegionHandler>("Region", region =>
    {
        region.RegisterServices((match, builder) =>
        {
            // overrides the default region registration above
            builder.Register(_ => RegionEndpoint.FromSystemName(match.Values["Region"]);
        });
    });
});

In the above example, any PathHandler underneath the /regions path will be injected the region from the current path. Any PathHandler outside of the /regions path will have the us-east-1 region injected.

Injecting an ancestor item

Sometimes PathHandler's need to know something about a specific item above them in the path hierarchy. You can have an ancestor item injected into your PathHandler's constructor by using the IItemAncestor<TItem> interface. For example, in this theoretical example, an EcsService handler wants to know what ECS cluster it belongs to, so it declares IItemAncestor<ClusterItem> as a constructor dependency:

public class EcsServiceHandler : PathHandler
{
    private readonly IItemAncestor<ClusterItem> _cluster;
    private readonly IEcsApi _ecs;

    public EcsServiceHandler(ItemPath path, IPathHandlerContext context, IItemAncestor<ClusterItem> cluster, IEcsApi ecs) : base(path, context)
    {
        _cluster = cluster;
        _ecs = ecs;
    }
    
    protected override IItem GetItemImpl()
    {
        var ecsService = _ecs.DescribeService(serviceName: ItemName, clusterName: _cluster.Name);
        
        return new EcsServiceItem(ParentPath, ecsService);
    }
}

This example assumes there is a IPathHandler higher in the routing hierarchy whose GetItem implementation returns an item of type ClusterItem. The IItemAncestor<TItem> implementation walks up the hierarchy looking for an item whose type matches the one declared as TItem.

Product Versions
.NET net6.0 net6.0-android net6.0-ios net6.0-maccatalyst net6.0-macos net6.0-tvos net6.0-windows net7.0 net7.0-android net7.0-ios net7.0-maccatalyst net7.0-macos net7.0-tvos net7.0-windows
Compatible target framework(s)
Additional computed target framework(s)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.7.0 92 1/28/2023
0.7.0-beta15 68 1/12/2023
0.7.0-beta14 47 1/12/2023
0.7.0-beta13 127 1/11/2023
0.7.0-beta12 46 1/11/2023
0.7.0-beta11 50 1/10/2023
0.7.0-beta10 46 1/10/2023
0.7.0-beta09 54 1/6/2023
0.7.0-beta08 53 1/5/2023
0.7.0-beta07 71 1/3/2023
0.7.0-beta06 56 12/3/2022
0.7.0-beta05 52 12/3/2022
0.7.0-beta04 89 11/7/2022
0.7.0-beta03 47 11/7/2022
0.7.0-beta02 49 11/7/2022
0.7.0-beta01 55 11/6/2022
0.6.0 240 9/17/2022
0.5.6 266 5/7/2022
0.5.5 235 5/7/2022
0.5.4 265 4/27/2022
0.5.3 254 4/27/2022
0.5.2 264 4/27/2022
0.5.1 250 4/26/2022
0.5.0 248 4/26/2022
0.4.0 345 2/2/2022
0.3.2 165 1/8/2022
0.3.1 170 1/7/2022
0.3.0 168 1/6/2022
0.2.1 174 1/4/2022
0.2.0 173 1/4/2022
0.1.4 173 1/3/2022
0.1.3 176 1/2/2022
0.1.2 171 1/2/2022
0.1.1 172 1/2/2022
0.1.0 166 1/2/2022
0.0.1-dev 96 1/2/2022

- BREAKING CHANGE: TypedString converted from record to class.
- Added the concept of a partial item to better support API's that return less detail when listing children compared to when getting an item by its id.
- Added a MapRecursive to the routing engine
- Exposed the Credentials associated with a PSDrive