A C# Framework to help building PowerShell Command providers. TreeStore is meant to cover the common PowerShell concepts of a file system as a hierarchical data structure of named items while the user (implementer of a concrete file system) can concentrate on the domain aspects of these nodes.
Simply build the solution.
dotnet build TreeStore.Core.slnxThe project references a current version of the PowerShell SDK . The project also targets net9.0 instead netstandard 2.0 and is therefore not portable between PS 5.1 and PS 7. Support of the older platform would require to change the code from C# 12 to C# 7.3 which is quite some work.
While the PowerShell Cmdlet provider is an abstraction of a file system it still uses the platform specific path separator: / for Linux and \ on Windows. TreeStore will follow this practice and will use the same separators as PowerShell. For input however TreeStore is as flexible as PowerShell and will consume both separators. Therefore in scripts the should be executable at both platforms use / as a separator for input.
The provider (implemented by TreeStoreCmdletProviderBase) inherits from PowerShells NavigationCmdletProvider which enables all Item-Cmdlets:
- Clear-Item
- Copy-Item
- Get-Item
- Invoke-Item
- Move-Item
- New-Item
- Remove-Item
- Rename-Item
- Set-Item
The provide alse implemen the IPropertyCmdletProvider enabling all non-.dynamic Item-Property cmdlets:
- Clear-ItemProperty
- Copy-ItemProperty
- Get-ItemProperty
- Set-ItemProperty
And it implements IDynamicPropertyCmdletProvider which enables all ItemProperty-Cmdlets interaction with dynamic properties:
- Move-ItemProperty
- New-ItemProperty
- Remove-ItemProperty
- Rename-ItemProperty
It also supports the IContentCmdletProvider for all Content cmdlets:
- Add-Content
- Clear-Content
- Get-Content
- Set-Content
A file system is a hierarchical data structure made from ProviderNode instances and are divided in containers and leaves:
- A
RootNode: provider node without a parent - A
ContainerNode: provider node with 0..n children and 1 parent - A
LeafNode: provider node without children and 1 parent
classDiagram
ContainerNode --|> ProviderNode
LeafNode --|> ProviderNode
RootNode --|> ContainerNode
class ContainerNode {
+ bool TryGetChildNode(string name, ProvoderNode childNode)
}
<<abstract>> ProviderNode
class ProviderNode {
+ string Name
+ IServiceProvider NodeServiceProvider
}
Each node has a reference to a user-provided instance of System.IServiceProvider. Through this service provider instance a nodes payload can decide dynamically which capability (file system operation) it may offer.
Read more about nodes at: ./src/TreeStore.Core/Nodes.
When accessing a node TreeStore.Core traverses the given the path and delegates the execution of the operation to the identified node:
%%{
init: {
"themeVariables": { 'noteTextColor': 'black' },
"sequence": {
"showSequenceNumbers":true,
"rightAngles":true
}
}
}%%
sequenceDiagram
PowerShell ->> TreeStoreCmdletProviderBase: invoke filesystem operation
TreeStoreCmdletProviderBase ->> TreeStoreCmdletProviderBase: traverse path to find ProviderNode
TreeStoreCmdletProviderBase ->> ProviderNode: invoke node operation
activate ProviderNode
ProviderNode ->> Payload: GetService(typeof(<capability>))
activate Payload
Payload -->> ProviderNode: reference to capability implementation
deactivate Payload
ProviderNode ->> Payload: invoke capability
deactivate ProviderNode
- PowerShell invokes a file system operation at the provider with a relative or absolute path(s)
TreeStoreCmdletProviderBasetraverses the path to identify the designated file system node to address. For most operations this is the actual node identified by the paths but some operations like container cmdlets e.g.Move-, Copy-, Remove-Itemthe parent of the node is addressed as the the 'owner' of its child nodes.- The identified node is invoked to perform the file system operation. The provide node will check if its payload implements the capability to handle the file system operation.
- The capability is invoked
Each node has a reference to a user provider data structure called 'payload'. This payload must implement IServiceProvider which is used by the TreeStore provider to ask for 'Capabilties'.
If a nodes operation is called by the PowerShell provider the provider node will ask the payloads service provider for the required capability interfaces to process the invocation. If the capability was provided it is called otherwise the node defaults.
An example implementation pattern for payload looks like this:
class Payload : IServiceProvider,
IGetChildItem // <- as an example of 'Capabilities' provided
{
// implement IServiceProvider
public object? GetService(Type serviceType)
{
if (this.GetType().IsAssignableTo(serviceType))
return this;
else return null;
}
// implement a provider node capability (doesn't has to be an explicit interface implementation)
bool IGetChildItem.HasChildItems(ICmdletProvider provider) {..}
IEnumerable<ProviderNode> IGetChildItem.GetChildItems(ICmdletProvider provider) {..}
}Read more about the capabilities at ./src/TreeStore.Core/Capabilities.
The sample file system 'DictionaryFS' provides a documented example how to implement a provider using TreeStore.Core based on nested IDictionary<string,object> instances.
This file system is also used to write integration tests for the provider logic in ./test/TreeStore.DictionaryFS.Test.