Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for additional APIs #66

Open
mattmcnabb opened this issue Oct 26, 2017 · 13 comments
Open

Support for additional APIs #66

mattmcnabb opened this issue Oct 26, 2017 · 13 comments

Comments

@mattmcnabb
Copy link

Currently SHiPS only implements Get-ChildItem functionality. Please consider adding additional capabilities, particularly removing items.

@stefanstranger
Copy link

stefanstranger commented Nov 27, 2017

Already some more updates on now Additional APIs? I want to use remove-item and Get-ItemProperty in my VSTeam SHiPS provider.

@jzabroski
Copy link
Contributor

jzabroski commented Feb 27, 2018

@jianyunt I think we should split this out into smaller feature requests:

  • Support Get-Help
  • Support Invoke-Item
  • Support Get-ItemProperty
  • Support Remove-Item
  • Support Remove-ItemProperty
  • Support Copy-Item
  • Support Copy-ItemProperty
  • Support Clear-Item
  • Support Clear-ItemProperty
  • Support Move-Item
  • Support Move-ItemProperty
  • Support New-Item
  • Support New-ItemProperty
  • Support Rename-Item
  • Support Rename-ItemProperty
  • Support Set-Item
  • Support Set-ItemProperty
  • Support Get-FormatData and possibly auto-generating .format.ps1xml files, possibly via the Prose Framework https://github.com/Microsoft/prose
  • Support Update-TypeData
  • Support Get-Content
  • Support Set-Content

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?

@michaeltlombardi
Copy link

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.

@oising
Copy link

oising commented Apr 9, 2018

Get-Content / Set-Content also.

@jzabroski
Copy link
Contributor

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.

@jianyunt
Copy link
Contributor

jianyunt commented Apr 9, 2018

@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.

@rhysjtevans
Copy link

rhysjtevans commented Apr 25, 2019

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

public virtual object[] GetChildItem()
?

I'll have a play in the meantime and see how I get on.

@SeeminglyScience
Copy link

@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.

@rhysjtevans
Copy link

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.

@oising
Copy link

oising commented May 1, 2019

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

@oising
Copy link

oising commented May 1, 2019

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.

@jzabroski
Copy link
Contributor

@oising Thanks for sharing your knowledge, wisdom, and design tips!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants