MountAnything 0.7.0
dotnet add package MountAnything --version 0.7.0
NuGet\Install-Package MountAnything -Version 0.7.0
<PackageReference Include="MountAnything" Version="0.7.0" />
<PackageVersion Include="MountAnything" Version="0.7.0" />
<PackageReference Include="MountAnything" />
paket add MountAnything --version 0.7.0
#r "nuget: MountAnything, 0.7.0"
#:package MountAnything@0.7.0
#addin nuget:?package=MountAnything&version=0.7.0
#tool nuget:?package=MountAnything&version=0.7.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
- Reference the
MountAnythingandMountAnything.Hosting.Buildnuget packages in your csproj project that will contain your powershell provider. - Create a class that implements the
IMountAnythingProviderinterface. - Implement the
CreateRoutermethod. 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-PSDrivecommand 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 optionalGetDefaultDrivesmethod in yourIMountAnythingProviderimplementation.
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 thePathHanderof this or any child route.Map<THandler,TCapture>- This is similar to theMapabove, 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 thePathHandlerof 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-Itemcommand is called. It should return theIItemthat corresponds to the path that thisPathHandleris processing. If no item exists at this path, it should returnnull.GetChildItemsImpl- This is called when theGet-ChildItemscommand. Its also used to support tab completion by default. It should return all of the child items of the item returned by theGetItemImplmethod.
In addition, you can optionally override the following methods when helpful/necessary:
ExistsImpl- By default, existence is checked by callingGetItemand determining if it returnednullor 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-Filterargument is used on theGet-ChildItemscommand. By default, theGetChildItemsImplmethod 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 byGetChildItemsImplare 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-Itemcommands.GetChildItemsCommandDefaultFreshness- This allows you to customize when the cache is used forGet-ChildItemscommands.
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. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net6.0
- Autofac.Extensions.DependencyInjection (>= 8.0.0)
- Microsoft.Extensions.DependencyInjection (>= 7.0.0)
- MountAnything.Hosting.Abstractions (>= 0.9.1)
- 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 | 607 | 1/28/2023 | |
| 0.7.0-beta15 | 292 | 1/12/2023 | |
| 0.7.0-beta14 | 261 | 1/12/2023 | |
| 0.7.0-beta13 | 493 | 1/11/2023 | |
| 0.7.0-beta12 | 297 | 1/11/2023 | |
| 0.7.0-beta11 | 291 | 1/10/2023 | |
| 0.7.0-beta10 | 285 | 1/10/2023 | |
| 0.7.0-beta09 | 302 | 1/6/2023 | |
| 0.7.0-beta08 | 259 | 1/5/2023 | |
| 0.7.0-beta07 | 273 | 1/3/2023 | |
| 0.7.0-beta06 | 287 | 12/3/2022 | |
| 0.7.0-beta05 | 285 | 12/3/2022 | |
| 0.7.0-beta04 | 325 | 11/7/2022 | |
| 0.7.0-beta03 | 267 | 11/7/2022 | |
| 0.7.0-beta02 | 281 | 11/7/2022 | |
| 0.7.0-beta01 | 282 | 11/6/2022 | |
| 0.6.0 | 664 | 9/17/2022 | |
| 0.5.6 | 629 | 5/7/2022 | |
| 0.5.5 | 582 | 5/7/2022 | |
| 0.5.4 | 735 | 4/27/2022 | |
| 0.5.3 | 611 | 4/27/2022 | |
| 0.5.2 | 611 | 4/27/2022 | |
| 0.5.1 | 611 | 4/26/2022 | |
| 0.5.0 | 598 | 4/26/2022 | |
| 0.4.0 | 741 | 2/2/2022 | |
| 0.3.2 | 485 | 1/8/2022 | |
| 0.3.1 | 446 | 1/7/2022 | |
| 0.3.0 | 465 | 1/6/2022 | |
| 0.2.1 | 465 | 1/4/2022 | |
| 0.2.0 | 462 | 1/4/2022 | |
| 0.1.4 | 474 | 1/3/2022 | |
| 0.1.3 | 480 | 1/2/2022 | |
| 0.1.2 | 468 | 1/2/2022 | |
| 0.1.1 | 429 | 1/2/2022 | |
| 0.1.0 | 483 | 1/2/2022 | |
| 0.0.1-dev | 355 | 1/2/2022 |
- Added support for Copy/Move/Rename commands
- Added support for Invoke-Item, Set-ItemProperty, Clear-ItemProperty, and Clear-Item
- Added an ItemNavigator base class to make it easier to support a recursive hierarchy of objects
- Added support for customizing the PSDriveInfo via the NewDrive method on the IMountAnythingProvider
- Added support for specifying a Root value when mounting a PSDrive
- Cleaned up the results of Get-ItemProperty
- Added a way to register services in the container via the more familiar IServiceCollection api. Autofac is still used behind the scenes due to its ability to have custom registrations in a nested lifetime scope.
BREAKING CHANGES:
- The IContentReaderHandler and IContentWriterHandler interfaces have been changed to be Stream based to simplify their typical implementation.
- Changed the default behavior of the Item base so that its TypeName is based on its class name instead of the UnderlyingObject's type.
- Updated NewItem method of the INewItemHandler to return the item that is created
- Renamed the RegisterServices to ConfigureContainer to match the ASP.NET Core naming scheme (in Startup)
- Updated the GetDefaultDrives signature to support creating subclasses of PSDriveInfo