Open mattmcnabb opened 6 years ago
Already some more updates on now Additional APIs? I want to use remove-item and Get-ItemProperty in my VSTeam SHiPS provider.
@jianyunt I think we should split this out into smaller feature requests:
I believe this is the full set of possible PowerShell Provider functions.
What do you think? Is there a reason these all need to be tackled at once?
Strong concur that tracking and adding support for these individually will lead to faster releases and more use for folks. Some of the above (New-Item, Remove-Item, and Copy-Item, for example) are much more valuable to a wider audience.
Get-Content / Set-Content also.
I updated my post to include Get-Content and Set-Content. Good idea. Is there a master list somewhere of "cool commands" that proxy for the old PS Drives feature? I compiled mine through scouring the Internet.
@jzabroski we do not have a list. Your list listed above can be considered as master lit. Agreed we should create separate items so the community may have chance to grab a few too.
Does anyone know if this is actively being worked on? Is it a case of adding in functions on the leaf and node classes, e.g. can it be done in the SHiPSBase so child classes inherit on the appropriate classes. Similar to https://github.com/PowerShell/SHiPS/blob/19b466bd0f000674e4ee787ad82d637b34360fd9/src/Microsoft.PowerShell.SHiPS/Node/SHiPSDirectory.cs#L35 ?
I'll have a play in the meantime and see how I get on.
@rhysjtevans You'd need to add it there as well as add the actual implementation for it in SHiPSProvider.
For example, if you wanted to add support for *-Content
you'd need to add (something like) these to SHiPSLeaf
:
public virtual object[] GetContent() => null;
public virtual void SetContent(object[] value) { }
public virtual void ClearContent() { }
And also the provider side implementation that utilizes SHiPSLeaf
:
public override IContentReader GetContentReader(string path)
{
// Create a content reader that uses SHiPSLeaf.
}
public override IContentWriter GetContentWriter(string path)
{
// Create a content writer that uses SHiPSLeaf.
}
public override void ClearContent(string path)
{
// Call SHiPSLeaf.ClearContent
}
Also dynamic params.
awesome, Nice one @SeeminglyScience. That helps. I'm working on a couple of other projects so may not get round to this for a while yet.
The approach I took when I wrote psproviderframework was to add two new cmdlets to encapsulate the idea of a content reader and a content writer, making it easier to abstract for differing backing stores - different nodes in the tree may require a different strategy for read/write. Each cmdlet took three scriptblocks as parameters representing read, seek and close operations respectively, just like a regular stream. Then, in the script-based provider definition, you provide functions for GetContentReader and GetContentWriter and construct readers and writers to return to the framework. Perhaps someone can take these ideas and work them into SHiPS. I wrote this about ten years ago, so powershell classes weren't around at the time, so it's all scriptblock based and relies on closures to capture state in the defining module.
Here's an example definition script of a simple provider that allows get-content and set-content against its nodes:
Import-Module c:\projects\PowerShell\PSProvider\PSProviderFramework\bin\Debug\PSProviderFramework.dll
if (Get-PSDrive data -ea 0) { remove-psdrive data }
# create new module representing our provider definition and pass to our hosting provider
New-PSDrive data ContainerScriptProvider -Root / -ModuleInfo $(New-Module -Name test -Args @{
# our backing store
a = "this is node a."
b = "this is node b."
c = "this is node c."
d = "this is node d."
} -ScriptBlock {
param($data)
function GetContentReader($path) {
$psprovider.writeverbose("getcontentreader '$path'")
# initialize for read operation
$item = $data[$path]
$content = [char[]]$item
$position = 0
# should close around our locals, esp. $position
# to support concurrent content readers.
& {
# create a new content reader and return it
New-ContentReader -OnRead {
param([long]$count)
# this implementation returns a string where $count represents number of char to return
# at a time; you may choose to return whatever you like, and treat $count in any way you feel
# is appropriate. All that matters is that you return an array. Return an empty array to signify
# the end of the stream.
# yes, i could use stringbuilder here but i figure the algorithm is more general purpose for a sample
# as this could be easily adopted for byte arrays.
$remaining = $content.length - $position
if ($remaining -gt 0) {
if ($count -gt $remaining) {
$len = $remaining
}
else {
$len = $count
}
$output = new-object char[] $len
[array]::Copy($content, $position, $output, 0, $len)
$position += $len
@($output -join "")
}
else {
# end stream, return empty array
write-verbose "read: EOF" -verbose
@()
}
} -OnSeek {
param([long]$offset, [io.seekorigin]$origin)
write-verbose "seek: $offset origin: $origin" -verbose
} -OnClose {
# perform any cleanup you like here.
write-verbose "read: close!" -verbose
}
}.getnewclosure() # capture state from module
}
function GetContentWriter($path) {
$psprovider.writeverbose("getcontentwriter '$path'")
# initialize for write operation
$item = $data[$path]
$position = 0
& {
New-ContentWriter -OnWrite {
param([collections.ilist]$content)
write-verbose "write: $($content.length) element(s)." -verbose
$content
} -OnSeek {
# seek must be implemented to support -Append, Add-Content etc
param([long]$offset, [io.seekorigin]$origin)
write-verbose "seek: $offset origin: $origin" -verbose
switch ($origin) {
"end" {
$position = $item.length + $offset
write-verbose "seek: new position at $position" -verbose
}
default {
write-warning "unsupported seek."
}
}
} -OnClose {
# perform any cleanup you like here.
write-verbose "write: close!" -verbose
}
}.getnewclosure() # capture state from module
}
function GetItem($path) {
$psprovider.writeverbose("getitem '$path'")
if ($path) {
if ($data[$path]) {
$psprovider.writeitemobject($data[$path], $path, $false)
}
}
else {
# root
$psprovider.writeitemobject($data.values, "/", $true)
}
}
function ItemExists($path) {
if ($path) {
$psprovider.writeverbose("item exists $path")
$data.containskey($path)
}
else {
# root always exists
$true
}
}
function GetChildNames($path, $returnContainers) {
$psprovider.writeverbose("getchildnames '$path' $returnContainers")
if ($path) {
if ($data[$path]) {
$psprovider.writeitemobject($path, $path, $false)
}
}
else {
$data.keys | % { $psprovider.writeitemobject($_, [string]$_, $false) }
}
}
function GetChildItems($path, $recurse) {
$psprovider.writeverbose("getchildnames '$path' $returnContainers")
if ($path) {
$psprovider.writeitemobject($data[$path], $_, $false)
}
else {
$data.keys | % {
$psprovider.writeitemobject($data[$_], $_, $false)
}
}
}
function ClearItem($path) {
$psprovider.writeverbose("clearitem '$path'")
}
})
get-content data:\a -verbose -ReadCount 8
See: https://github.com/oising/psprovider/blob/master/Trunk/PSProviderFramework/Scripts/harness.ps1
I even got as far as providing transaction support ;)
namespace PSProviderFramework
{
[CmdletProvider("TransactedTreeScriptProvider", ProviderCapabilities.ShouldProcess | ProviderCapabilities.Transactions)]
public class TransactedTreeScriptProvider : TreeScriptProvider {
protected override TReturn InvokeFunction<TReturn>(string function, params object[] parameters) {
TReturn returnValue;
// push correct provider thread context for this call
using (PSProviderContext<TransactedTreeScriptProvider>.Enter(this)) {
returnValue = PSProviderContext<TransactedTreeScriptProvider>
.InvokeFunctionInternal<TReturn>(function, parameters);
} // pop context
return returnValue;
}
protected override void InvokeFunction(string function, params object[] parameters) {
// push correct provider thread context for this call
using (PSProviderContext<TransactedTreeScriptProvider>.Enter(this)) {
PSProviderContext<TransactedTreeScriptProvider>
.InvokeFunctionInternal<object>(function, parameters);
} // pop context
}
}
[CmdletProvider("TreeScriptProvider", ProviderCapabilities.ShouldProcess)]
public class TreeScriptProvider : NavigationCmdletProvider, IScriptProvider, IContentCmdletProvider, IPropertyCmdletProvider
{
// ...
... as well as properties read/write, container, tree etc. In fact, I think I had covered every aspect of providers. And they haven't changed since then.
@oising Thanks for sharing your knowledge, wisdom, and design tips!
Currently SHiPS only implements Get-ChildItem functionality. Please consider adding additional capabilities, particularly removing items.