在编写PowerShell Cmdlet时,如何处理路径?
本文关键字:处理 路径 何处理 PowerShell Cmdlet | 更新日期: 2023-09-27 18:17:45
在编写c# cmdlet时,接收文件作为参数的正确方法是什么?到目前为止,我只有一个字符串属性LiteralPath(与它们的参数命名约定保持一致)。这是一个问题,因为你只能得到输入到控制台上的任何内容;可以是完整路径,也可以是相对路径。
使用Path.GetFullPath(string)不起作用。它认为我现在在~,我不是。如果我将属性从字符串更改为FileInfo,也会出现同样的问题。
编辑:对于任何感兴趣的人,这个解决方法对我来说是有效的:
SessionState ss = new SessionState();
Directory.SetCurrentDirectory(ss.Path.CurrentFileSystemLocation.Path);
LiteralPath = Path.GetFullPath(LiteralPath);
LiteralPath是字符串参数。我仍然有兴趣学习处理作为参数传递的文件路径的推荐方法。
EDIT2:这是更好的,这样你就不会打乱用户的当前目录,你应该把它设置回来。
string current = Directory.GetCurrentDirectory();
Directory.SetCurrentDirectory(ss.Path.CurrentFileSystemLocation.Path);
LiteralPath = Path.GetFullPath(LiteralPath);
Directory.SetCurrentDirectory(current);
这是一个非常复杂的领域,但我在这方面有丰富的经验。简而言之,有一些cmdlet直接接受来自系统的win32路径。IO api,这些通常使用-FilePath参数。如果您想编写一个性能良好的"powershelly"cmdlet,您需要-Path和-LiteralPath来接受管道输入,并使用相对和绝对提供程序路径。这是我不久前写的一篇博客文章的节选:
PowerShell中的路径一开始很难理解。PowerShell路径——或PSPaths,不要与Win32路径混淆——在它们的绝对形式中,它们有两种不同的风格:
- 供应商合格:
FileSystem::c:'temp'foo.txt
PSDrive-qualified:
c:'temp'foo.txt
很容易混淆提供者内部路径(已解析的System.Management.Automation.PathInfo
的ProviderPath
属性—上面的提供者限定路径::
右边的部分)和驱动限定路径,因为如果您查看默认的文件系统提供程序驱动器,它们看起来是相同的。也就是说,PSDrive的名字(C)与本地备份存储,windows文件系统(C)相同。因此,为了让您更容易理解差异,请创建一个新的PSDrive:
ps c:'> new-psdrive temp filesystem c:'temp'
ps c:'> cd temp:
ps temp:'>
现在,让我们再看一遍:
- Provider-qualified:
FileSystem::c:'temp'foo.txt
- Drive-qualified:
temp:'foo.txt
这次比较容易看出有什么不同。提供者名称右侧的粗体文本是ProviderPath。
因此,您编写一个通用的提供程序友好的Cmdlet(或高级函数)的目标是接受路径:- 定义别名为
PSPath
的LiteralPath
路径参数 - 定义一个
Path
参数(它将解析通配符/glob) - 总是假设你正在接收pspath,而不是本地提供程序路径(例如Win32路径)
第三点尤其重要。而且,显然LiteralPath
和Path
应该属于互斥的参数集。
相对路径
一个很好的问题是:我们如何处理传递给Cmdlet的相对路径?因为您应该假设提供给您的所有路径都是pspath,所以让我们看看下面的Cmdlet做了什么:ps temp:'> write-zip -literalpath foo.txt
该命令应该假设foo.txt在当前驱动器中,因此应该立即在ProcessRecord或EndProcessing块中解决这个问题,例如(使用这里的脚本API来演示):
$provider = $null;
$drive = $null
$pathHelper = $ExecutionContext.SessionState.Path
$providerPath = $pathHelper.GetUnresolvedProviderPathFromPSPath(
"foo.txt", [ref]$provider, [ref]$drive)
现在您已经具备了重新创建两种绝对形式的pspath所需的一切,并且您还拥有了本机绝对的ProviderPath。要为foo.txt创建一个提供程序限定的PSPath,请使用$provider.Name + “::” + $providerPath
。如果$drive
不是$null
(您的当前位置可能是提供者限定的,在这种情况下$drive
将是$null
),那么您应该使用$drive.name + ":'" + $drive.CurrentLocation + "'" + "foo.txt"
来获得驱动器限定的PSPath。
c#快速入门
下面是一个c#提供程序感知cmdlet的框架,可以帮助您了解。它内置了检查,以确保它已被传递给文件系统提供程序路径。我正在为NuGet打包它,以帮助其他人编写行为良好的提供者感知cmdlet: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)
{
// 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;
}
}
}
cmlet开发指南(Microsoft)
从长远来看,这里有一些更普遍的建议,应该会对你有所帮助:http://msdn.microsoft.com/en-us/library/ms714657%28VS.85%29.aspx