Minding Path Inputs in a Cmdlet


When I was a Monad neophyte, I was asked to write a Cmdlet taking a file path as a parameter. A big mistake I made was not keeping in mind that in Monad the FileSystem provider was just one of the many providers. (This makes Monad different from many other shells where you are always in a directory.) For a path-taking Cmdlet, this means two things. The first is that the input path can be specifying a non-FileSystem provider such as the registry provider. The second is that the Cmdlet can be invoked from a non-FileSystem provider. This makes a relative input path not relative to the FileSystem provider. Besides the provider issues, a path in Monad can also be drive-qualified or home-relative. Also, a drive can be mounted at a location that is not the root of the file system.

To deal with all these conditions, the Cmdlet needs to call SessionState.Path.GetResolvedProviderPathFromMshPath and checks the provider returned . If the path does not exist, SessionState.Path.GetUnresolvedProviderPathFromMshPath is used. However, as of Beta 3, this API does not return the provider. We are looking to add a version that does return the provider in V1. As usual, we can’t promise this will happen.

In future versions, we hope to provide enough functionalities so that Cmdlets that writes to a path can use InvokeProvider.Content.GetWriter(path). This enables the Cmdlet to write to any provider that implements the IContentCmdletProvider interface. Moreover, the Cmdlet does not need to worry about what provider a path specifies. Again, we can’t promise this will happen.


Kevin Loo [MSFT]
Microsoft Command Shell Development
Microsoft Corporation
This posting is provided “AS IS” with no warranties, and confers no rights.






Comments (7)

  1. Pauli Swanson says:

    After reading this posting, I found out all of my cmdlets that write to files would fail when invoked from anywhere outside of the file system provider. Now, I need to go back and fix them.

    Thanks for bringing this up.

  2. Nick Howell says:

    I notice you didn’t say anything about the MshPath attribute; this seems to me to be the preferable way of doing things…

    Also, how should we handle writing binary files? We can’t really get a stream to write to, can we? Do we just have to go through the System.IO namespace?

    Nick

  3. MSDNArchive says:

    The MshPath is a quick way to get path resolution but it has a couple of limitations. First, it will only resolve to existing paths so if you want to allow for the creation of a new item then you can’t use MshPath as it will result in an error. Second, MshPath doesn’t implement the recommended error reporting due to some limits in the information available to the attributes. We recommend that failure to resolve a path be written to the error pipe using WriteError() so that if more paths are being piped to the command the operation can proceed if these paths are resolved. Since the attributes don’t have access to the pipelines a failure to resolve the path results in an exception which terminates the pipeline.

    I would only recommend that the MshPath attribute be used if the path parameter doesn’t take pipeline input and the item must exist for the operation to proceed. I do however recommend that you have a parameter named "Path" with an alias of "MshPath" that takes either ValueFromPipeline or ValueFromPipelineByPropertyName and is of type String[]. When ProcessRecord is called you can use the APIs mentioned by Kevin above to resolve the path. At that point you can use the system APIs directly to accomplish your operation (ie System.IO.*). You must make sure the path resolved to the appropriate provider for those APIs though.

    The limitation of not being able to write binary to a file is a limitation on the InvokeProvider.* APIs that are not exposing the dynamic parameters of the provider. For now you must either use InvokeCommand.Invoke() to call the "set-content" cmdlet or do the path resolution and system APIs I mention above.

    Jeff Jones

    Technical Lead

    Monad Development

    Microsoft Corporation

  4. nlhowell says:

    I can’t seem to get set-content to work with binary data, or maybe it’s get-content.

    For example, if I have a binary file "test.doc" and I want to copy it to test2.doc:

    new-item test2.doc -t file; get-content test.doc | set-content test2.doc;

    test2.doc is not a valid Word file, even though test.doc is. What *should* I be doing here?

    Thanks for the responsiveness!

    Nick

  5. nlhowell says:

    Thanks for the advice! I’ve switched all my cmdlets to take strings and run them through the resolution methods.

    As far as binary content, I can’t seem to get set-content to work (or maybe it’s get-content):

    get-content test.doc | set-content test2.doc

    Word says that test2.doc is not a valid Word file after doing this, but test.doc is. Am I doing something wrong?

    Nick

  6. MSDNArchive says:

    Try using the -encoding parameter for set-content.  It can take a value of "byte".

    Jeff Jones

    Technical Lead

    Monad Development

    Microsoft Corporation

    This posting is provided "AS IS" with no warranties, and confers no rights.

  7. nlhowell says:

    That worked; thanks a lot!

    Another question: would it be possible to factor the encoding functionality into something public? It seems that it would apply to more providers than just FS (for example, I use an Encoding parameter on get-uri, a wget analog).

    Thanks again,

    Nick