2011-12-14 20 views
34

¿Cuál es la forma correcta de recibir un archivo como parámetro al escribir un cmdlet C#? Hasta ahora solo tengo una propiedad LiteralPath (que se alinea con su convención de nomenclatura de parámetros) que es una cadena. Esto es un problema porque solo obtiene lo que está escrito en la consola; que podría ser la ruta completa o podría ser una ruta relativa.¿Cómo trato las rutas al escribir un cmdlet de PowerShell?

El uso de Path.GetFullPath (cadena) no funciona. Piensa que estoy actualmente en ~, no lo estoy. El mismo problema ocurre si cambio la propiedad de una cadena a un FileInfo.

EDIT: Para todos los interesados, esta solución está funcionando para mí:

SessionState ss = new SessionState(); 
    Directory.SetCurrentDirectory(ss.Path.CurrentFileSystemLocation.Path); 

    LiteralPath = Path.GetFullPath(LiteralPath); 

LiteralPath es el parámetro de cadena. Todavía estoy interesado en aprender cuál es la forma recomendada de manejar las rutas de archivos que se pasan como parámetros.

EDIT2: Esto es mejor, para que no se meta con el directorio actual de los usuarios, debe volver a configurarlo.

  string current = Directory.GetCurrentDirectory(); 
      Directory.SetCurrentDirectory(ss.Path.CurrentFileSystemLocation.Path); 
      LiteralPath = Path.GetFullPath(LiteralPath); 
      Directory.SetCurrentDirectory(current); 
+0

Pista: siempre debe obtener el SessionState del ExecutionContext del Cmdlet. – x0n

+1

Parece que está buscando [PSCmdlet.GetUnresolvedProviderPathFromPSPath Method] (http://msdn.microsoft.com/en-us/library/system.management.automation.pscmdlet.getunresolvedproviderpathfpspspath (v = VS.85) .aspx) –

+0

Esta fue mi salvación. ¡Saludo! – Simon

Respuesta

59

Esta es un área sorprendentemente compleja, pero tengo mucha experiencia aquí. En resumen, hay algunos cmdlets que aceptan rutas win32 directamente desde las API de System.IO, y estas normalmente usan un parámetro -FilePath. Si desea escribir un cmdlet "powershelly" bien gestionado, necesita -Path y -LiteralPath, para aceptar la entrada de canalización y trabajar con rutas de proveedor relativas y absolutas. He aquí un extracto de un post que escribí hace un tiempo: [. En un principio]

Caminos en PowerShell son difíciles de entender PowerShell Caminos - o PSPaths, que no debe confundirse con las rutas de Win32 - en sus formas absolutas, vienen en dos sabores distintos:

  • proveedor cualificado: FileSystem::c:\temp\foo.txt
  • PSDrive cualificado: c:\temp\foo.txt

Es muy fácil confundirse sobre provider-internal (La propiedad ProviderPath de System.Management.Automation.PathInfo resuelto - la parte a la derecha de :: de la ruta calificada por el proveedor anterior) y las rutas calificadas por la unidad ya que tienen el mismo aspecto si mira las unidades de proveedor de FileSystem predeterminadas. Es decir, el PSDrive tiene el mismo nombre (C) que el almacén de respaldo nativo, el sistema de archivos de Windows (C). Por lo tanto, para que sea más fácil para usted para entender las diferencias, crear usted mismo una nueva PSDrive:

ps c:\> new-psdrive temp filesystem c:\temp\ 
ps c:\> cd temp: 
ps temp:\> 

Ahora, vamos a ver esto de nuevo:

  • proveedor cualificado: FileSystem::c:\temp\foo.txt
  • Drive- calificado: temp:\foo.txt

Es un poco más fácil esta vez para ver qué es diferente esta vez. El texto en negrita a la derecha del nombre del proveedor es ProviderPath.

Por lo tanto, sus objetivos para escribir un cmdlet generalizada proveedor de usar (o de funciones avanzadas) que acepte caminos son:

  • definir un parámetro LiteralPath ruta alias a PSPath
  • definir un parámetro Path (que se resolver los comodines/glob)
  • siempre ha de asumir que está recibiendo PSPaths, NO proveedor senderos nativos (por ejemplo, caminos Win32)

El punto número tres es especialmente importante. Además, obviamente LiteralPath y Path deben pertenecer a conjuntos de parámetros mutuamente excluyentes.

rutas relativas

Una buena pregunta es: ¿Cómo tratamos con rutas relativas que se pasa a un cmdlet. A medida que se debe asumir todos los caminos están dando a usted es PSPaths, vamos a ver a continuación lo hace el cmdlet:

ps temp:\> write-zip -literalpath foo.txt 

El comando debe asumir foo.txt se encuentra en la unidad actual, por lo que este debe ser resuelto de inmediato en el ProcessRecord o EndProcessing bloque como (usando la API de secuencias de comandos aquí para demostración):

$provider = $null; 
$drive = $null 
$pathHelper = $ExecutionContext.SessionState.Path 
$providerPath = $pathHelper.GetUnresolvedProviderPathFromPSPath(
    "foo.txt", [ref]$provider, [ref]$drive) 

Ahora todo lo necesario para recrear las dos formas absolutas de PSPaths, y también tiene la ProviderPath absoluta nativa. Para crear un PSPath calificado por el proveedor para foo.txt, use $provider.Name + “::” + $providerPath. Si $drive no es $null (su ubicación actual puede ser un proveedor calificado en cuyo caso $drive será $null) entonces debe usar $drive.name + ":\" + $drive.CurrentLocation + "\" + "foo.txt" para obtener un PSPath calificado para el controlador.

de inicio rápido C# Esqueleto

Aquí está un esqueleto de un cmdlet C# proveedor de cuenta para que te va. Ha incorporado comprobaciones para garantizar que se le haya entregado una ruta de proveedor de FileSystem.Estoy en el proceso de envasado de esto para NuGet para ayudar a otros a obtener la escritura de buen comportamiento cmdlets proveedor consciente:

using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Management.Automation; 
using Microsoft.PowerShell.Commands; 
namespace PSQuickStart 
{ 
    [Cmdlet(VerbsCommon.Get, Noun, 
     DefaultParameterSetName = ParamSetPath, 
     SupportsShouldProcess = true) 
    ] 
    public class GetFileMetadataCommand : PSCmdlet 
    { 
     private const string Noun = "FileMetadata"; 
     private const string ParamSetLiteral = "Literal"; 
     private const string ParamSetPath = "Path"; 
     private string[] _paths; 
     private bool _shouldExpandWildcards; 
     [Parameter(
      Position = 0, 
      Mandatory = true, 
      ValueFromPipeline = false, 
      ValueFromPipelineByPropertyName = true, 
      ParameterSetName = ParamSetLiteral) 
     ] 
     [Alias("PSPath")] 
     [ValidateNotNullOrEmpty] 
     public string[] LiteralPath 
     { 
      get { return _paths; } 
      set { _paths = value; } 
     } 
     [Parameter(
      Position = 0, 
      Mandatory = true, 
      ValueFromPipeline = true, 
      ValueFromPipelineByPropertyName = true, 
      ParameterSetName = ParamSetPath) 
     ] 
     [ValidateNotNullOrEmpty] 
     public string[] Path 
     { 
      get { return _paths; } 
      set 
      { 
       _shouldExpandWildcards = true; 
       _paths = value; 
      } 
     } 
     protected override void ProcessRecord() 
     { 
      foreach (string path in _paths) 
      { 
       // This will hold information about the provider containing 
       // the items that this path string might resolve to.     
       ProviderInfo provider; 
       // This will be used by the method that processes literal paths 
       PSDriveInfo drive; 
       // this contains the paths to process for this iteration of the 
       // loop to resolve and optionally expand wildcards. 
       List<string> filePaths = new List<string>(); 
       if (_shouldExpandWildcards) 
       { 
        // Turn *.txt into foo.txt,foo2.txt etc. 
        // if path is just "foo.txt," it will return unchanged. 
        filePaths.AddRange(this.GetResolvedProviderPathFromPSPath(path, out provider)); 
       } 
       else 
       { 
        // no wildcards, so don't try to expand any * or ? symbols.      
        filePaths.Add(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(
         path, out provider, out drive)); 
       } 
       // ensure that this path (or set of paths after wildcard expansion) 
       // is on the filesystem. A wildcard can never expand to span multiple 
       // providers. 
       if (IsFileSystemPath(provider, path) == false) 
       { 
        // no, so skip to next path in _paths. 
        continue; 
       } 
       // at this point, we have a list of paths on the filesystem. 
       foreach (string filePath in filePaths) 
       { 
        PSObject custom; 
        // If -whatif was supplied, do not perform the actions 
        // inside this "if" statement; only show the message. 
        // 
        // This block also supports the -confirm switch, where 
        // you will be asked if you want to perform the action 
        // "get metadata" on target: foo.txt 
        if (ShouldProcess(filePath, "Get Metadata")) 
        { 
         if (Directory.Exists(filePath)) 
         { 
          custom = GetDirectoryCustomObject(new DirectoryInfo(filePath)); 
         } 
         else 
         { 
          custom = GetFileCustomObject(new FileInfo(filePath)); 
         } 
         WriteObject(custom); 
        } 
       } 
      } 
     } 
     private PSObject GetFileCustomObject(FileInfo file) 
     { 
      // this message will be shown if the -verbose switch is given 
      WriteVerbose("GetFileCustomObject " + file); 
      // create a custom object with a few properties 
      PSObject custom = new PSObject(); 
      custom.Properties.Add(new PSNoteProperty("Size", file.Length)); 
      custom.Properties.Add(new PSNoteProperty("Name", file.Name)); 
      custom.Properties.Add(new PSNoteProperty("Extension", file.Extension)); 
      return custom; 
     } 
     private PSObject GetDirectoryCustomObject(DirectoryInfo dir) 
     { 
      // this message will be shown if the -verbose switch is given 
      WriteVerbose("GetDirectoryCustomObject " + dir); 
      // create a custom object with a few properties 
      PSObject custom = new PSObject(); 
      int files = dir.GetFiles().Length; 
      int subdirs = dir.GetDirectories().Length; 
      custom.Properties.Add(new PSNoteProperty("Files", files)); 
      custom.Properties.Add(new PSNoteProperty("Subdirectories", subdirs)); 
      custom.Properties.Add(new PSNoteProperty("Name", dir.Name)); 
      return custom; 
     } 
     private bool IsFileSystemPath(ProviderInfo provider, string path) 
     { 
      bool isFileSystem = true; 
      // check that this provider is the filesystem 
      if (provider.ImplementingType != typeof(FileSystemProvider)) 
      { 
       // create a .NET exception wrapping our error text 
       ArgumentException ex = new ArgumentException(path + 
        " does not resolve to a path on the FileSystem provider."); 
       // wrap this in a powershell errorrecord 
       ErrorRecord error = new ErrorRecord(ex, "InvalidProvider", 
        ErrorCategory.InvalidArgument, path); 
       // write a non-terminating error to pipeline 
       this.WriteError(error); 
       // tell our caller that the item was not on the filesystem 
       isFileSystem = false; 
      } 
      return isFileSystem; 
     } 
    } 
} 

Directrices para el desarrollo de cmdlet (Microsoft)

He aquí algunos consejos más generalizada que serle de ayuda en el largo plazo: http://msdn.microsoft.com/en-us/library/ms714657%28VS.85%29.aspx

+3

Es por eso que utilizo StackOverflow. Gracias. Enlace a su publicación de blog? Me suscribiré. – Matthew

+0

Buscar http://www.nivot.org - He estado un poco tranquilo recientemente debido al trabajo, pero con PowerShell v3, estoy tratando de acelerar nuevamente. – x0n

+0

Oisin, gracias por su trabajo aquí. ¿Es esto lo más simple que puede ser? En lo que respecta a la usabilidad API, es una de las peores que he visto en .NET: es probable que simplemente ignore esta BS y utilice strings. –

6

Así es como se puede manejar Path de entrada en un script de PowerShell cmdlet:

function My-Cmdlet { 
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact='Medium')] 
    Param(
     # The path to the location of a file. You can also pipe a path to My-Cmdlet. 
     [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] 
     [string[]] $Path 
    ) 

    Begin { 
     ... 
    } 

    Process { 
     # ignore empty values 
     # resolve the path 
     # Convert it to remove provider path 
     foreach($curPath in ($Path | Where-Object {$_} | Resolve-Path | Convert-Path)) { 
      # test wether the input is a file 
      if(Test-Path $curPath -PathType Leaf) { 
       # now we have a valid path 

       # confirm 
       if ($PsCmdLet.ShouldProcess($curPath)) { 
        # for example 
        Write-Host $curPath 
       } 
      } 
     } 
    } 

    End { 
     ... 
    } 
} 

Puede invocar este método en las siguientes maneras:

Con una trayectoria directa:

My-Cmdlet . 

Con una cadena de caracteres comodín:

My-Cmdlet *.txt 

Con un archivo real:

My-Cmdlet .\PowerShell_transcript.20130714003415.txt 

Con un conjunto de archivos en una variable:

$x = Get-ChildItem *.txt 
My-Cmdlet -Path $x 

O con el nombre solamente:

My-Cmdlet -Path $x.Name 

O por pasing el conjunto de archivos a través de la tubería:

$x | My-Cmdlet 
+1

Tiene un par de problemas con esto: acepta string [] $ path, pero lo está tratando como una cadena única. También podría usar el cmdlet resolve-path en lugar del uso más complejo de join-path. – x0n

+0

@ x0n se resolvieron los problemas. –

Cuestiones relacionadas