How to access a PSDrive from System.IO.File calls?

1.1k views Asked by At

I'm writing a c# cmdlet which copies files from one location to another (similar to rsync). It even supports ToSession and FromSession.

I'd like it to work with PSDrives that use the Filesystem provider but it currently throws an error from System.IO.File.GetAttributes("psdrive:\path")

I'd really like to use calls from System.IO on the PSDrive.

How does something like copy-item do this?

I've performed a search for accessing PSDrives from c# and have returned no results.

This is the equivalent of my code

new-psdrive -name mydrive -psprovider filesystem  -root \\aserver\ashare -Credential domain\user
$attributes=[system.io.file]::GetAttributes("mydrive:\path\")

returns

Exception calling "GetAttributes" with "1" argument(s): "The given path's format is not supported."
1

There are 1 answers

0
mklement0 On BEST ANSWER

.NET knows nothing about PowerShell drives (and typically also has a different working directory), so conversion to a filesystem-native path is necessary:

In PowerShell code:

Use Convert-Path to convert a PowerShell-drive-based path to a native filesystem path that .NET types understand:

$attributes=[System.IO.File]::GetAttributes((Convert-Path "mydrive:\path\"))

By default (positional argument use) and with -Path, Convert-Path performs wildcard resolution; to suppress the latter, use the -LiteralPath parameter.

Caveat: Convert-Path only works with existing paths. Lifting that restriction is the subject of the feature request in GitHub issue #2993.


In C# code:

In PSCmdlet-derived cmdlets:

  • Use GetUnresolvedProviderPathFromPSPath() to translate a PS-drive-based path into a native-drive-based path[1] unresolved, which means that, aside from translating the drive part:

    • the existence of the path is not verified (but the drive name must exist)
    • and no wildcard resolution is performed.
  • Use GetResolvedProviderPathFromPSPath() to resolve a PS-drive-based path to a native-drive-based one, which means that, aside from translating the drive part:

    • wildcard resolution is performed, yielding potentially multiple paths or even none.
    • literal path components must exist.
  • Use the CurrentProviderLocation() method with provider ID "FileSystem" to get the current filesystem location's path as a System.Management.Automation.PathInfo instance; that instance's .Path property and .ToString() method return the PS form of the path; use the .ProviderPath property to get the native representation.

Here's a simple ad-hoc compiled cmdlet that exercises both methods:

# Compiles a Get-NativePath cmdlet and adds it to the session.
Add-Type @'
    using System;
    using System.Management.Automation;
    [Cmdlet("Get", "NativePath")]
    public class GetNativePathCommand : PSCmdlet {

        [Parameter(Mandatory=true,Position=0)]
        public string PSPath { get; set; }

        protected override void ProcessRecord() {
            WriteObject("Current directory:");
            WriteObject("  PS form:      " + CurrentProviderLocation("FileSystem"));
            WriteObject("  Native form:  " + CurrentProviderLocation("FileSystem").ProviderPath);
            //
            WriteObject("Path argument in native form:");
            WriteObject("  Unresolved:");
            WriteObject("    " + GetUnresolvedProviderPathFromPSPath(PSPath));
            //
            WriteObject("  Resolved:");
            ProviderInfo pi;
            foreach (var p in GetResolvedProviderPathFromPSPath(PSPath, out pi))
            {
              WriteObject("    " + p);
            }
        }
    }
'@ -PassThru | % Assembly | Import-Module

You can test it as follows:

# Create a foo: drive whose root is the current directory.
$null = New-PSDrive foo filesystem .

# Change to foo:
Push-Location foo:\

# Pass a wildcard path based on the new drive to the cmdlet
# and have it translated to a native path, both unresolved and resolved;
# also print the current directory, both in PS form and in native form.
Get-NativePath foo:\*.txt

If your current directory is C:\Temp and it happens to contain text files a.txt and b.txt, you'll see the following output:

Current directory:
  PS form:      foo:\
  Native form:  C:\Temp\
Path argument in native form:
  Unresolved:
    C:\Temp\*.txt
  Resolved:
    C:\Temp\a.txt
    C:\Temp\b.txt

[1] If a PS drive (created with New-PSDrive) referenced in the input path is defined in terms of a UNC path, the resulting native path will be a UNC path too.