2011-12-14 28 views
34

Cách thích hợp để nhận tệp như một tham số khi viết lệnh C#? Cho đến nay tôi chỉ có một thuộc tính LiteralPath (sắp xếp với quy ước đặt tên tham số của chúng), đó là một chuỗi. Đây là một vấn đề bởi vì bạn chỉ nhận được bất cứ điều gì được gõ vào giao diện điều khiển; đó có thể là con đường đầy đủ hoặc có thể là một con đường tương đối.Làm cách nào để xử lý Đường dẫn khi viết PowerShell Cmdlet?

Sử dụng Path.GetFullPath (chuỗi) không hoạt động. Nó nghĩ rằng tôi hiện đang ở ~, tôi không. Cùng một vấn đề xảy ra nếu tôi thay đổi thuộc tính từ một chuỗi thành một FileInfo.

EDIT: Đối với bất cứ ai quan tâm, việc này đang làm việc cho tôi:

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

    LiteralPath = Path.GetFullPath(LiteralPath); 

LiteralPath là tham số chuỗi. Tôi vẫn quan tâm đến việc tìm hiểu cách được đề nghị để xử lý đường dẫn tệp được chuyển thành tham số.

EDIT2: Tốt hơn, để bạn không gây rối với thư mục hiện tại của người dùng, bạn nên đặt lại.

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

Gợi ý: bạn nên luôn lấy SessionState từ ExecutionContext của Cmdlet. – x0n

+1

Có vẻ như bạn đang tìm kiếm [Phương thức PSCmdlet.GetUnresolvedProviderPathFromPSPath] (http://msdn.microsoft.com/en-us/library/system.management.automation.pscmdlet.getunresolvedproviderpathfrompspath (v = VS.85) .aspx) –

+0

Đây là sự cứu rỗi của tôi. Chào! – Simon

Trả lời

59

Đây là khu vực phức tạp đáng ngạc nhiên, nhưng tôi có rất nhiều kinh nghiệm ở đây. Tóm lại, có một số lệnh ghép ngắn chấp nhận đường dẫn win32 trực tiếp từ API System.IO, và thường sử dụng tham số -FilePath. Nếu bạn muốn viết một lệnh "powershelly" tốt, bạn cần -Path và -LiteralPath, để chấp nhận đầu vào đường ống và làm việc với các đường dẫn nhà cung cấp tương đối và tuyệt đối. Dưới đây là một đoạn trích từ một bài đăng trên blog mà tôi đã viết cách đây không lâu:

Đường dẫn trong PowerShell khó hiểu [lúc đầu]. họ có hai hương vị riêng biệt:

  • Provider-trình độ: FileSystem::c:\temp\foo.txt
  • PSDrive trình độ: c:\temp\foo.txt

Nó rất dễ dàng để bị lẫn lộn trên provider-internal (thuộc tính ProviderPath của một giải quyết System.Management.Automation.PathInfo - phần bên phải của :: của đường dẫn đủ điều kiện của nhà cung cấp ở trên) và đường dẫn đủ điều kiện vì chúng trông giống nhau nếu bạn nhìn vào ổ đĩa nhà cung cấp hệ thống FileSystem mặc định. Tức là, PSDrive có cùng tên (C) làm cửa hàng sao lưu gốc, hệ thống tệp windows (C). Vì vậy, để làm cho nó dễ dàng hơn cho chính mình để hiểu được sự khác biệt, tạo cho mình một PSDrive mới:

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

Bây giờ, chúng ta hãy xem xét điều này một lần nữa:

  • Provider-trình độ: FileSystem::c:\temp\foo.txt
  • Drive- đủ điều kiện: temp:\foo.txt

Lần này dễ dàng hơn để xem thời điểm này khác. Văn bản in đậm ở bên phải của tên nhà cung cấp là ProviderPath.

Vì vậy, mục tiêu của mình để viết một lệnh tổng quát cung cấp dịch vụ thân thiện (hoặc chức năng nâng cao) chấp nhận đường dẫn là:

  • Xác định một tham số LiteralPath đường aliased để PSPath
  • Xác định một tham số Path (mà sẽ giải quyết các ký tự đại diện/glob)
  • Luôn giả sử bạn đang nhận PSPath, KHÔNG phải đường dẫn nhà cung cấp gốc (ví dụ: đường dẫn Win32)

Điểm số ba đặc biệt quan trọng. Ngoài ra, rõ ràng là LiteralPathPath phải thuộc về bộ tham số loại trừ lẫn nhau.

Paths tương đối

Một câu hỏi tốt là: làm thế nào để chúng ta đối phó với đường dẫn tương đối được truyền cho một lệnh. Như bạn nên cho tất cả các đường dẫn được trao cho bạn là PSPaths, chúng ta hãy nhìn vào những gì các Cmdlet dưới đây không:

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

Lệnh nên cho foo.txt là trong ổ đĩa hiện tại, vì thế này nên được giải quyết ngay lập tức trong ProcessRecord hoặc EndProcessing khối tương tự (sử dụng API scripting đây để demo):

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

Bây giờ bạn có tất cả mọi thứ bạn cần phải tạo hai hình thức tuyệt đối của PSPaths, và bạn cũng có ProviderPath tuyệt đối tự nhiên. Để tạo PSPath đủ điều kiện cho nhà cung cấp cho foo.txt, hãy sử dụng $provider.Name + “::” + $providerPath. Nếu $drive không phải là $null (vị trí hiện tại của bạn có thể là nhà cung cấp đủ điều kiện trong trường hợp $drive sẽ là $null) thì bạn nên sử dụng $drive.name + ":\" + $drive.CurrentLocation + "\" + "foo.txt" để có PSPath đủ điều kiện lái xe.

Quickstart C# Skeleton

Dưới đây là một bộ xương của một C# cung cấp-aware cmdlet để giúp bạn đi. Nó đã được xây dựng trong kiểm tra để đảm bảo nó đã được giao một đường dẫn nhà cung cấp FileSystem.Tôi đang trong quá trình đóng gói này lên cho NuGet để giúp đỡ người khác nhận bằng văn bản well-behaved Cmdlets nhà cung cấp-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; 
     } 
    } 
} 

Hướng dẫn phát triển Cmdlet (Microsoft)

Dưới đây là một số lời khuyên tổng quát hơn mà sẽ giúp bạn ra ngoài trong thời gian dài: http://msdn.microsoft.com/en-us/library/ms714657%28VS.85%29.aspx

+3

Đây là lý do tại sao tôi sử dụng StackOverflow. Cảm ơn bạn. Liên kết tới bài đăng trên blog của bạn? Tôi sẽ đăng ký. – Matthew

+0

Tìm kiếm http://www.nivot.org - Gần đây tôi đã yên lặng một chút do công việc, nhưng với powerhell v3, tôi đang cố gắng sửa đổi lại. – x0n

+0

Oisin, cảm ơn vì công việc của bạn ở đây. Đây có phải là đơn giản nhất có thể không? Theo như khả năng sử dụng API đi, một trong những tồi tệ nhất tôi đã nhìn thấy trong. NET - Tôi có khả năng chỉ cần bỏ qua BS này và sử dụng dây. –

6

Đây là cách bạn có thể xử lý Path đầu vào trong một lệnh PowerShell kịch bản:

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

Bạn có thể gọi phương pháp này trong các cách sau:

Với một đường dẫn trực tiếp:

My-Cmdlet . 

Với một chuỗi ký tự đại diện:

My-Cmdlet *.txt 

Với một tập tin thực tế:

My-Cmdlet .\PowerShell_transcript.20130714003415.txt 

Với một tập hợp các tập tin trong một biến:

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

Hoặc với cái tên duy nhất:

My-Cmdlet -Path $x.Name 

Hoặc bởi Pasing tập các tập tin thông qua các đường ống dẫn:

$x | My-Cmdlet 
+1

Bạn có một vài vấn đề với điều này - bạn chấp nhận chuỗi [] $ đường dẫn, nhưng bạn đang coi nó như một chuỗi số ít. Bạn cũng có thể sử dụng lệnh ghép ngắn đường dẫn giải quyết thay vì sử dụng đường dẫn nối phức tạp hơn. – x0n

+0

@ x0n đã được giải quyết. –

Các vấn đề liên quan