这是一个非常复杂的领域,但我在这方面有很丰富的经验。简单来说,一些cmdlet直接使用System.IO API中的win32路径,并且这些通常使用-FilePath参数。如果您想编写符合规范的 "powershelly" cmdlet,则需要-Path和-LiteralPath来接受管道输入并处理相对和绝对提供程序路径。以下是我之前写的博客文章摘录:
PowerShell中的路径很难理解。 PowerShell路径或称为PSPaths(不要与Win32路径混淆)在其绝对形式中有两种完全不同的风格:
- 提供程序限定:FileSystem :: c:\temp\foo.txt
- PSDrive限定:c:\temp\foo.txt
从默认的FileSystem提供程序驱动器查看,很容易混淆提供程序内部(解析的System.Management.Automation.PathInfo的ProviderPath属性-提供程序限定路径上::右侧的部分)和驱动器限定路径,因为它们看起来相同。也就是说,PSDrive的名称(C)与本地后备存储(Windows文件系统(C))相同。因此,为了更容易地理解差异,请创建新的PSDrive。
ps c:\> new-psdrive temp filesystem c:\temp\
ps c:\> cd temp:
ps temp:\>
现在,让我们再来看一下这个问题:
提供程序限定:
FileSystem::c:\temp\foo.txt
驱动器限定:
temp:\foo.txt
这次更容易看出不同之处了。提供程序名称右侧的粗体文本是ProviderPath。
因此,编写一个通用的提供程序友好的Cmdlet(或高级函数),接受路径的目标是:
定义别名为
PSPath
的
LiteralPath
路径参数
定义
Path
参数(将解析通配符/ glob)
始终假设您收到的是PSPaths,而不是本机提供程序路径(例如Win32路径)
第三点尤其重要。此外,明显的
LiteralPath
和
Path
应该属于互斥的参数集。
相对路径
一个很好的问题是:我们如何处理传递给Cmdlet的相对路径。由于您应该假设所有给定的路径都是PSPaths,让我们看一下下面的Cmdlet所做的事情:
ps temp:\> write-zip -literalpath foo.txt
该命令应假定foo.txt在当前驱动器中,因此应立即在ProcessRecord或EndProcessing块中解决此问题(使用脚本API进行演示):
该命令应该默认foo.txt在当前驱动器中,所以在ProcessRecord或EndProcessing块中应立即解决这个问题(这里使用脚本API进行演示):
$provider = $null;
$drive = $null
$pathHelper = $ExecutionContext.SessionState.Path
$providerPath = $pathHelper.GetUnresolvedProviderPathFromPSPath(
"foo.txt", [ref]$provider, [ref]$drive)
现在你已经掌握了重新创建两个PSPaths的绝对形式所需的一切,同时也有了本机的绝对ProviderPath。要为foo.txt创建一个提供程序限定的PSPath,请使用
$provider.Name + "::" + $providerPath
。如果
$drive
不是
$null
(您当前的位置可能是提供程序限定的,在这种情况下
$drive
将为
$null
),则应使用
$drive.name + ":\\" + $drive.CurrentLocation + "\\" + "foo.txt"
来获取驱动器限定的PSPath。
C#快速入门骨架
以下是一个C#提供程序感知Cmdlet的骨架,它内置了检查以确保已传递FileSystem提供程序路径。我正在将其打包为NuGet,以帮助其他人编写行为良好的提供程序感知Cmdlets:
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(
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)
{
ProviderInfo provider;
PSDriveInfo drive;
List<string> filePaths = new List<string>();
if (_shouldExpandWildcards)
{
filePaths.AddRange(this.GetResolvedProviderPathFromPSPath(path, out provider));
}
else
{
filePaths.Add(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(
path, out provider, out drive));
}
if (IsFileSystemPath(provider, path) == false)
{
continue;
}
foreach (string filePath in filePaths)
{
PSObject custom;
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)
{
WriteVerbose("GetFileCustomObject " + file);
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)
{
WriteVerbose("GetDirectoryCustomObject " + dir);
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;
if (provider.ImplementingType != typeof(FileSystemProvider))
{
ArgumentException ex = new ArgumentException(path +
" does not resolve to a path on the FileSystem provider.");
ErrorRecord error = new ErrorRecord(ex, "InvalidProvider",
ErrorCategory.InvalidArgument, path);
this.WriteError(error);
isFileSystem = false;
}
return isFileSystem;
}
}
}
Cmdlet开发指南(Microsoft)
以下是一些更为通用的建议,这些建议将有助于您长期发展:
http://msdn.microsoft.com/en-us/library/ms714657%28VS.85%29.aspx