Windows PowerShell providers offer an abstraction layer to access data stores and their components, which would not be accessible otherwise through the command line. In this post you will learn how to create your own custom PowerShell provider.

The main purpose is to make the data store accessible to your console in a familiar fashion like a file system, hiding all underlying complexities and implementations.

Run the following commands in your PowerShell console to see the built-in PowerShell providers and drives that can access the provider's information store.

# to list built-in PowerShell providers and drives
Get-PSProvider
Get-PSDrive
List built in PowerShell providers and drives

List built in PowerShell providers and drives

Simple Hierarchy in PowerShell (SHiPS) ^

Microsoft offers a PowerShell provider framework as a module called SHiPS. This lets us author custom PowerShell providers. SHiPS is based on another module called the PowerShell Provider Framework (P2F) developed by PowerShell MVP Jim Christopher. P2F works under the hood to develop a hierarchical information store into PowerShell providers.

The term hierarchy is relevant because information stores are hierarchical structures that consist of three types of node elements:

  1. Root node: A top-level node used as an entry point to access the information store.
  2. Container node: A second-level directory that is a child of the root node or another container node and holds child nodes or items. A root node is also a container node, but with no parent nodes.
  3. Leaf node: The lowest-level node that doesn't have a child node, where actual data sits in a SHiPS hierarchy.

From a PowerShell console launched as an administrator, run the below command to install the SHiPS module from the PowerShell gallery:

Install-Module SHiPS

Create a custom PowerShell Provider ^

Let's take this website as an example, which is also a hierarchical information structure somewhat represented in the following image.

Hierarchical information store

Hierarchical information store

To implement this information store as a PowerShell drive, we first have to write a library of PowerShell classes that inherit public classes and types from the SHiPS namespace. So a custom PowerShell provider begins by adding the SHiPS namespace at the top like in the below example:

using namespace Microsoft.PowerShell.SHiPS

Then you define the root class for the top-level node, which is also the entry point to this hierarchical information store. In our example, the root class is named Blog. The root node is also a container node, as it holds child items and containers that must inherit from the SHiPSDirectory class. But to differentiate a root node from any other container node, you have to define a constructor with a node name as a parameter like in the following code sample:

using namespace Microsoft.PowerShell.SHiPS
# defining root directory
class Blog : SHiPSDirectory
{
    # constructor with same name as a parameter
    Blog($name) : Base($name) {}
}

The root node alone doesn't fulfill our needs because we also need to define its child nodes. So we will implement a method called GetChildItem() in the root class to return the child nodes, which will complete the root node definition in the information store hierarchy.

using namespace Microsoft.PowerShell.SHiPS

# defining root directory
class Blog : SHiPSDirectory
{
    # constructor with node name as a parameter
    Blog($name) : Base($name) {}

    # return child nodes
    [Object[]] GetChildItem(){
        $obj = @()
        $obj += [Category]::new()
        $obj += [Author]::new()
        return $obj
    }
}

This implementation will depend upon your information store and will vary from one use case to another. Once you've defined the root node, it's now time to define the container nodes that are children to the root node. In our example these are Category and Author.

# defining container node 1
class Category : SHiPSDirectory {

    Category():base('Category'){}

    [Object[]] GetChildItem(){
        $obj = @()
        $obj += [PowerShell]::new()
        $obj += [Security]::new()
        return $obj
    }
}

# defining container node 2
class Author : SHiPSDirectory {

    Author():base('Author'){}

    [Object[]] GetChildItem(){
        return [Prateek]::new()
    }
}

Container node definitions don't require constructors to have node names as parameters for nodes. But you still need to implement the GetChildItem() method to return the PowerShell and Security child nodes of the Category container and Prateek as the child of the Author container.

In our example, PowerShell, Security, and Prateek are the lowest-level nodes with no child items. This is why we define leaf node classes by inheriting from the SHiPSLeaf class like in following code sample. Because these are leaf nodes, we won't implement the GetChildItem method in these classes.

# defining leaf node
class PowerShell : SHiPSLeaf {
    # define an optional leaf property
    $link = 'https://4sysops.com/tag/powershell/'
    PowerShell():base('PowerShell'){}
}

class Security : SHiPSLeaf {
    $link = 'https://4sysops.com/tag/security/'
    Security():base('Security'){}
}

class Prateek : SHiPSLeaf {
    $link = 'https://4sysops.com/members/prateeksingh/'
    Prateek():base('Prateek'){}
}

Now put all of these classes together in a file and save it as a PowerShell module. For example, we will name this 4SysOps.psm1, and the complete module will look like the code below. It is also called a provider data module because it holds all the data for the provider.

using namespace Microsoft.PowerShell.SHiPS

# defining root directory
class Blog : SHiPSDirectory
{
    Blog($name) : Base($name) {}

    [Object[]] GetChildItem(){
        $obj =  @()
        $obj += [Category]::new()
        $obj += [Author]::new()
        return $obj
    }
}

# defining container nodes
class Category : SHiPSDirectory {

    Category():base('Category'){}

    [Object[]] GetChildItem(){
        $obj =  @()
        $obj += [PowerShell]::new()
        $obj += [Security]::new()
        return $obj
    }
}
class Author : SHiPSDirectory {

    Author():base('Author'){}

    [Object[]] GetChildItem(){
        return [Prateek]::new()
    }
}

# defining leaf nodes
class PowerShell : SHiPSLeaf {
    # define an optional leaf property
    $link =  'https://4sysops.com/tag/powershell/'
    PowerShell():base('PowerShell'){}
}

class Security : SHiPSLeaf {
    $link =  'https://4sysops.com/tag/security/'
    Security():base('Security'){}
}

class Prateek : SHiPSLeaf {
    $link =  'https://4sysops.com/members/prateeksingh/'
    Prateek():base('Prateek'){}
}

Creating a new drive with a custom provider ^

Import the SHiPS module and the provider data module saved from the subsection above into your current session and run a New-PSDrive cmdlet on this custom provider data.

The following syntax creates a PowerShell drive from SHiPS-based provider data:

New-PSDrive -name YourDriveName -PSProvider SHiPS -root 'YourModuleName#RootClass'

So creating a new drive will look like this:

Import-Module SHiPS
Import-Module C:\Temp\4SysOps.psm1
New-PSDrive -Name 4sysops -PSProvider SHiPS -Root "4SysOps#Blog"
Create a new drive using the provider module

Create a new drive using the provider module

After creating the drive, you can navigate it just like any other file system using the commands cd or Set‑Location to change directories and Get-ChildItem, dir, or ls to list child items under a directory.

Subscribe to 4sysops newsletter!

Navigating a SHiPS based PowerShell drive

Navigating a SHiPS based PowerShell drive

Conclusion ^

One of the most popular use cases of SHiPS in Azure is Azure Cloud Shell. This lets you access all of your AzureRM resources from your PowerShell console by adding an abstraction layer on top of all underlying complexities. This step-by-step article demonstrates how you can develop any information store into PowerShell providers using the SHiPS module, which you can access from the PowerShell console.

avatar
3 Comments
  1. Manuel Berfelde 3 years ago

    if only new-item, remove-item, copy and move worked...

    • Author

      I agree you can not use New, Remove, Copy, Move-Item, because even though the information store looks like a File System it is just a model of a another system with different types/objects which has nothing to do with Files or directories. But we can write custom functions or use modules that work for the system we are modeling by importing them in the SHiPS provider module.

      For example the Azure Drive in Azure cloud shell which is also a SHiPS implementation, if you navigate to ./Subscription/VirtualMachines/ you can use  New-AzureRmVM command to create a New Item (Virtual Machine) under that directory and if you look closely it uses the AzureRM.Compute module which has been imported to fulfill such needs.
      PS Azure:\> cd ./Pay-As-You-Go/VirtualMachines/
      PS Azure:\> Get-AzureRmCommand | Where-Object Name -like "New-*"
      CommandType Name Version Source
      ----------- ---- ------- ------
      Cmdlet New-AzureRmVM 0.12.0 AzureRM.Compute.Netcore
      Cmdlet New-AzureRmVMConfig 0.12.0 AzureRM.Compute.Netcore
      Cmdlet New-AzureRmVMDataDisk 0.12.0 AzureRM.Compute.Netcore

      Following is a link if you want to take a look how AzurePSDrive is Implemented using SHiPS - https://github.com/PowerShell/AzurePSDrive/blob/development/AzurePSDrive.psm1

  2. Artem 2 years ago

    Could I see the items of the hierarchy as folders and files in Windows Explorer if I mount PSDrive with -Persist option?

Leave a reply

Please enclose code in pre tags

Your email address will not be published. Required fields are marked *

*

© 4sysops 2006 - 2021

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account