PowerShell: Creating a PSDrive Using SHiPS Provider.
Introduction.
SHiPS (Simple Hierarchy in PowerShell) is a PowerShell provider which is capable of representing any structured data like a file system. SHiPS directory will be used by mounting it as a drive using New-PSDrive cmdlet. Once mounted, we can navigate through the structured data using basic navigation cmdlets.
Supported platforms.
SHiPS is supported in below operating systems.
- A Windows machine with PowerShell 5 or later.
- Linux or Mac with PowerShell 6.x
How to install SHiPS.
SHiPS can be installed from PowerShell gallery.
Install-Module -Name SHiPS
Note: This article will target Windows Firewall to represent as a file system.
Design of the file system.
FWL:
|__
Inbound
|__Domain
|__Private
|__Public
|__
Outbound
|__Domain
|__Private
|__Public
SHiPS powered PS Provider development is done as a PowerShell module.Module development will be keeping the above directory structure in mind.
The root directory is used as a mapped drive using New-PSDrive cmdlet.
New-PSDrive -Name FWL -PSProvider SHiPS -Root FirewallDrive#FireWallRoot
Here,
FirewallDrive – Is the module name.
FireWallRoot – Is the Class name which has to be defined first.
PSProvider development using SHiPS has to be done using Classes. SHiPS requires at least a RootModule class which will be inheriting SHiPSDirectory class from Microsoft.PowerShell.SHiPS namespace.
The first line of the module will be importing the namespace followed by the first class.
using namespace Microsoft.PowerShell.SHiPS
[SHiPSProvider(UseCache = $false)]
class FirewallRoot : SHiPSDirectory
{
# A default constructor is mandatory.
FirewallRoot([string]$name):base($name)
{
}
[object[]] GetChildItem()
{
$Directions = @('Inbound','Outbound')
$Output = $Directions | ForEach-Object -Process {
[Direction]::new($_)
}
return $Output
}
}
In above code snippet, FirewallRoot class will represent the Root directory of this Provider and will inherit from the SHiPSDirectory class from Microsoft.PowerShell.SHiPS namespace.
The code below, which is GetChildItem() method is the place where we specify what should happen when we do Get-ChildItem from the Root directory. This method will return object(s) which will be the output of the dir or Get-ChildItem cmdlet.
As you can see, GetChildItem method is creating a new instance of Direction class for each items in Directions which are Inbound and Outbound.
Let’s have a look on Direction class.
[SHiPSProvider(UseCache = $True)]
class Direction : SHiPSDirectory
{
[string]$Direction
Direction([string]$Name):base($Name)
{
$this.Direction = $Name
}
[object[]] GetChildItem ()
{
# Listing all Profiles regardless of current direction.
$Profiles = Get-NetFirewallProfile | ForEach-Object -Process {
[Profile]::new($_.Name,$_)
}
return $Profiles
}
}
As you can see, direction class is also having a constructor which is responsible of adding the Name and Mode property to the output of Get-ChildItem from the root directory, this can be extended by defining more members to the direction class.
Since this class is defined as a directory, there should be a GetChildItem() method as it is there for the root directory class. Here the method will actually query the firewall rules in the local machine and will create instances of Profile class. Profile class will be responsible for the output of dir from Directions directory (either Inbound or Outbound),
Below is how the profile class is defined.
[SHiPSProvider(UseCache = $true)]
class Profile : SHiPSDirectory
{
[object] $ProfileType
[String] $Enabled
[String] $LogAllowed
[String] $LogMaxSizeKilobytes
Profile([string]$name,$CurrentProfile):base($name)
{
$this.ProfileType = $CurrentProfile
$this.LogAllowed = $CurrentProfile.LogAllowed
$this.LogMaxSizeKilobytes = $CurrentProfile.LogMaxSizeKilobytes
$this.Enabled = $CurrentProfile.Enabled
}
[object[]] GetChildItem ()
{
$FirewallRules = Get-NetFirewallRule -AssociatedNetFirewallProfile $this.ProfileType | ForEach-Object -Process {
[FirewallRule]::New($_.Name,$_)
}
return $FirewallRules
}
}
This class is the last SHiPSDirectory class in this provider. You can see there is a constructor which sets multiple properties of the directory apart from Mode and Name property. The Name property here is transformed Profile using Format files for this module. And again, when we do a Get-ChildItem, the GetChildItem() method will get invoked which will call the Get-NetFirewallRule for the current profile. Current profile here is the Name property as you can see that is passed as one of the parameters for creating FirewallRule instance for each rule.
Below is how the FirewallRule class looks like.
[SHiPSProvider(UseCache = $true)]
class FirewallRule : SHiPSLeaf
{
[String] $RuleDescription
[String] $RuleProfile
[String] $Name
[String] $RuleState
[String] $RuleStatus
FirewallRule([string]$Name, [object]$Rule):base($name)
{
$this.Name = $Name
$this.RuleDescription = $Rule.Description
$this.RuleProfile = $Rule.Profile
$this.RuleStatus = $Rule.Status
$this.RuleState = 'Enabled'
if( $Rule.Enabled -eq 'False' ){
$this.RuleState = 'Disabled'
}
}
}
This is the last class in this provider and is the leaf of the directory structure, hence it has no GetChildItem() method and has to be inherited from SHiPSLeaf class which is in Microsoft.PowerShell.SHiPS namespace. The constructor is responsible for showing the result of Get-ChildItem from the Profile directory and the items to display is restricted and controlled in this constructor.
As you can see there is a property which is set as UseCache for each Class. This property will cache the output if set to true which will help in reusing the data for repeated executions of Get-ChildItem. This can be overridden using –Force parameter of Get-ChildItem.
Below is the output of the whole execution for this article.