MountAnything 0.7.0-beta02
See the version list below for details.
dotnet add package MountAnything --version 0.7.0-beta02
NuGet\Install-Package MountAnything -Version 0.7.0-beta02
<PackageReference Include="MountAnything" Version="0.7.0-beta02" />
paket add MountAnything --version 0.7.0-beta02
#r "nuget: MountAnything, 0.7.0-beta02"
// Install MountAnything as a Cake Addin #addin nuget:?package=MountAnything&version=0.7.0-beta02&prerelease // Install MountAnything as a Cake Tool #tool nuget:?package=MountAnything&version=0.7.0-beta02&prerelease
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
- Reference the
MountAnything
andMountAnything.Hosting.Build
nuget packages in your csproj project that will contain your powershell provider. - Create a class that implements the
IMountAnythingProvider
interface. - Implement the
CreateRouter
method. For information on creating a router, see the Router section below. - 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 theNew-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 optionalGetDefaultDrives
method in yourIMountAnythingProvider
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 thePathHander
of this or any child route.Map<THandler,TCapture>
- This is similar to theMap
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 childPathHandler
.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 thePathHandler
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 theGet-Item
command is called. It should return theIItem
that corresponds to the path that thisPathHandler
is processing. If no item exists at this path, it should returnnull
.GetChildItemsImpl
- This is called when theGet-ChildItems
command. Its also used to support tab completion by default. It should return all of the child items of the item returned by theGetItemImpl
method.
In addition, you can optionally override the following methods when helpful/necessary:
ExistsImpl
- By default, existence is checked by callingGetItem
and determining if it returnednull
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 theGet-ChildItems
command. By default, theGetChildItemsImpl
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 byGetChildItemsImpl
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 returningfalse
.GetItemCommandDefaultFreshness
- This allows you to customize when the cache is used forGet-Item
commands.GetChildItemsCommandDefaultFreshness
- This allows you to customize when the cache is used forGet-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 Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net6.0
- Autofac (>= 6.3.0)
- MountAnything.Hosting.Abstractions (>= 0.9.0)
- System.Management.Automation (>= 7.2.0)
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 | 417 | 1/28/2023 | |
0.7.0-beta15 | 178 | 1/12/2023 | |
0.7.0-beta14 | 149 | 1/12/2023 | |
0.7.0-beta13 | 379 | 1/11/2023 | |
0.7.0-beta12 | 179 | 1/11/2023 | |
0.7.0-beta11 | 180 | 1/10/2023 | |
0.7.0-beta10 | 175 | 1/10/2023 | |
0.7.0-beta09 | 186 | 1/6/2023 | |
0.7.0-beta08 | 154 | 1/5/2023 | |
0.7.0-beta07 | 164 | 1/3/2023 | |
0.7.0-beta06 | 175 | 12/3/2022 | |
0.7.0-beta05 | 177 | 12/3/2022 | |
0.7.0-beta04 | 210 | 11/7/2022 | |
0.7.0-beta03 | 166 | 11/7/2022 | |
0.7.0-beta02 | 179 | 11/7/2022 | |
0.7.0-beta01 | 171 | 11/6/2022 | |
0.6.0 | 536 | 9/17/2022 | |
0.5.6 | 491 | 5/7/2022 | |
0.5.5 | 459 | 5/7/2022 | |
0.5.4 | 510 | 4/27/2022 | |
0.5.3 | 476 | 4/27/2022 | |
0.5.2 | 472 | 4/27/2022 | |
0.5.1 | 471 | 4/26/2022 | |
0.5.0 | 463 | 4/26/2022 | |
0.4.0 | 589 | 2/2/2022 | |
0.3.2 | 333 | 1/8/2022 | |
0.3.1 | 300 | 1/7/2022 | |
0.3.0 | 320 | 1/6/2022 | |
0.2.1 | 319 | 1/4/2022 | |
0.2.0 | 311 | 1/4/2022 | |
0.1.4 | 322 | 1/3/2022 | |
0.1.3 | 330 | 1/2/2022 | |
0.1.2 | 319 | 1/2/2022 | |
0.1.1 | 296 | 1/2/2022 | |
0.1.0 | 337 | 1/2/2022 | |
0.0.1-dev | 214 | 1/2/2022 |
Added support for Copy/Move/Rename commands
BREAKING CHANGE:
The IContentReaderHandler and IContentWriterHandler interfaces have been changed to be Stream based to simplify their typical implementation.