2011-12-14 19 views
34

Qual è il modo corretto di ricevere un file come parametro durante la scrittura di un C# cmdlet? Finora ho solo una proprietà LiteralPath (che si allinea con la loro convenzione di denominazione dei parametri) che è una stringa. Questo è un problema perché si ottiene semplicemente ciò che viene digitato nella console; quale potrebbe essere il percorso completo o potrebbe essere un percorso relativo.Come faccio a trattare con i percorsi quando si scrive una PowerShell Cmdlet?

Utilizzando Path.GetFullPath (stringa) non funziona. Pensa che io sia attualmente a ~, non lo sono. Lo stesso problema si verifica se cambio la proprietà da una stringa a un FileInfo.

EDIT: Per chiunque sia interessato, questa soluzione sta lavorando per me:

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

    LiteralPath = Path.GetFullPath(LiteralPath); 

LiteralPath è il parametro di stringa. Sono ancora interessato ad apprendere quale sia il modo consigliato di gestire i percorsi dei file passati come parametri.

EDIT2: Questo è meglio, in modo che non si scherza con gli utenti directory corrente, è necessario impostare nuovamente.

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

Suggerimento: dovrebbe sempre ottenere SessionState da ExecutionContext del cmdlet. – x0n

+1

Sembra che tu stia cercando [PSCmdlet.GetUnresolvedProviderPathFromPSPath Method] (http://msdn.microsoft.com/en-us/library/system.management.automation.pscmdlet.getunresolvedproviderpathfrompspath (v = VS.85) .aspx) –

+0

Questa è stata la mia salvezza. Saluto! – Simon

risposta

59

Questa è una zona sorprendentemente complesso, ma ho un sacco di esperienza qui. In breve, ci sono alcuni cmdlet che accettano i percorsi win32 direttamente dalle API System.IO, e in genere usano un parametro -FilePath. Se si vuole scrivere un "powershelly" cmdlet ben educati, è necessario -Path e -LiteralPath, di accettare input da pipeline e lavorare con i percorsi di provider relativi e assoluti. Ecco un estratto da un post che ho scritto qualche tempo fa: [. In un primo momento]

percorsi in PowerShell sono difficili da capire PowerShell Paths - o PSPaths, da non confondere con i percorsi Win32 - nelle loro forme assolute, essi sono di due tipi distinti:

  • Provider-qualificati: FileSystem::c:\temp\foo.txt
  • PSDrive qualificato: c:\temp\foo.txt

E 'molto facile da ottenere confuso su fornitore-interna (Il ProviderPath proprietà di un deliberato System.Management.Automation.PathInfo - la parte a destra del :: del percorso del provider qualificato sopra) e percorso di unità qualificati in quanto lo stesso aspetto se si guardano le unità provider predefinito FileSystem. Vale a dire, PSDrive ha lo stesso nome (C) del backing store nativo, il filesystem di Windows (C). Quindi, per rendere più facile per te per capire le differenze, crea un nuovo PSDrive:

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

Ora, diamo un'occhiata a questo nuovo:

  • Provider-qualificata: FileSystem::c:\temp\foo.txt
  • Drive- qualificata: temp:\foo.txt

Un po 'più facile questa volta per vedere cosa c'è di diverso questa volta. Il testo in grassetto a destra del nome del provider è ProviderPath.

Quindi, i tuoi obiettivi per la scrittura di un cmdlet generalizzata fornitore-friendly (o funzioni avanzate) che accetta i percorsi sono:

  • definire un parametro LiteralPath percorso alias di PSPath
  • definire un parametro Path (che sarà risolvere i caratteri jolly/glob)
  • assumere sempre si ricevono PSPaths, NON provider-percorsi nativo (ad esempio percorsi Win32)

Il punto numero tre è particolarmente importante. Inoltre, ovviamente LiteralPath e Path dovrebbero appartenere a set di parametri mutuamente esclusivi.

percorsi relativi

Una buona domanda è: come possiamo affrontare i percorsi relativi vengono passati a un cmdlet. Come si dovrebbe assumere tutti i percorsi di essere dato a sei PSPaths, diamo un'occhiata a ciò che il Cmdlet di seguito fa:

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

Il comando deve assumere foo.txt è l'unità corrente, quindi questo dovrebbe essere risolto immediatamente a ProcessRecord o EndProcessing blocco simile (utilizzando l'API di scripting qui per demo):

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

Ora è tutto il necessario per ricreare le due forme assolute di PSPaths, e hai anche la ProviderPath assoluto nativo. Per creare un PSPath qualificato dal provider per foo.txt, utilizzare $provider.Name + “::” + $providerPath. Se $drive non è $null (la posizione corrente potrebbe essere qualificata dal fornitore, nel qual caso $drive sarà $null), quindi utilizzare $drive.name + ":\" + $drive.CurrentLocation + "\" + "foo.txt" per ottenere un PSPath qualificato per l'unità.

Quickstart C# scheletro

Ecco uno scheletro di un C# fornitore-aware cmdlet per farti andare. Ha incorporato controlli per assicurarsi che gli sia stato assegnato un percorso del provider FileSystem.Io sono nel processo di confezionamento questo per NuGet per aiutare gli altri ottenere la scrittura ben educati Cmdlet fornitore-aware:

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; 
     } 
    } 
} 

linee guida di sviluppo cmdlet (Microsoft)

Ecco alcuni consigli più generalizzata che vi possono aiutare nel lungo periodo: http://msdn.microsoft.com/en-us/library/ms714657%28VS.85%29.aspx

+3

Ecco perché utilizzo StackOverflow. Grazie. Link al tuo post sul blog? Mi iscriverò. – Matthew

+0

Cerca http://www.nivot.org - Sono stato un po 'tranquillo di recente a causa del lavoro, ma con powershell v3 fuori, sto cercando di fare di nuovo il giro. – x0n

+0

Oisin, grazie per il tuo lavoro qui. È questo il più semplice che può essere? Per quanto riguarda l'usabilità dell'API, è uno dei peggiori che abbia mai visto in .NET. È probabile che ignori questa BS e utilizzi le stringhe. –

6

in questo modo è possibile gestire Path ingresso in un cmdlet di script PowerShell:

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 { 
     ... 
    } 
} 

È possibile richiamare questo metodo nei seguenti modi:

Con un percorso diretto:

My-Cmdlet . 

Con una stringa di caratteri jolly:

My-Cmdlet *.txt 

Con un file vero e proprio:

My-Cmdlet .\PowerShell_transcript.20130714003415.txt 

Con una serie di file in una variabile:

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

O con il solo nome:

My-Cmdlet -Path $x.Name 

O per pasing l'insieme di file attraverso il gasdotto:

$x | My-Cmdlet 
+1

Hai un paio di problemi con questo - accetti stringa [] $ percorso, ma lo stai trattando come una stringa singolare. È anche possibile utilizzare il cmdlet resolp-path anziché l'uso più complesso del join-path. I problemi – x0n

+0

@ x0n sono stati risolti. –