これは驚くほど複雑な領域ですが、私はここでたくさんの経験を持っています。つまり、System.IO APIから直接win32パスを受け入れるコマンドレットがいくつかあり、これらは通常、-FilePathパラメーターを使用します。正常に動作する「powershelly」コマンドレットを作成する場合は、パイプライン入力を受け入れ、相対プロバイダーパスと絶対プロバイダーパスを操作するために、-Pathと-LiteralPathが必要です。これは私が少し前に書いたブログ投稿からの抜粋です:
PowerShellのパスは[最初は]理解するのが難しいです。PowerShellパス(またはPSPaths、Win32パスと混同しないでください)は、絶対的な形式で、2つの異なるフレーバーで提供されます。
- プロバイダー認定:
FileSystem::c:\temp\foo.txt
- PSDrive認定済み:
c:\temp\foo.txt
デフォルトのファイルシステムプロバイダードライブを見ると同じように見えるため、プロバイダー内部(ProviderPath
解決済みのプロパティSystem.Management.Automation.PathInfo
-上記のプロバイダー修飾パスの右側の部分)とドライブ修飾パスについて混乱するのは非常に簡単です。 ::
。つまり、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です。
したがって、パスを受け入れる一般化されたプロバイダーフレンドリーなコマンドレット(または高度な関数)を作成するための目標は次のとおりです。
LiteralPath
次のエイリアスのパスパラメータを定義しますPSPath
- パラメータを定義し
Path
ます(ワイルドカード/グロブを解決します)
- ネイティブプロバイダーパス(Win32パスなど)ではなく、PSPathを受信していると常に想定してください。
ポイント3は特に重要です。また、明らかに、相互に排他的なパラメータセットに属する必要がLiteralPath
あります。Path
相対パス
良い質問は、コマンドレットに渡される相対パスをどのように処理するかです。与えられているすべてのパスがPSPathであると想定する必要があるため、以下のコマンドレットの機能を見てみましょう。
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の2つの絶対形式を再作成するために必要なすべてのものが得られ、ネイティブの絶対ProviderPathもあります。foo.txtのプロバイダー修飾PSPathを作成するには、を使用します$provider.Name + “::” + $providerPath
。そう$drive
でない場合$null
(現在の場所はプロバイダー認定である可能性があります$drive
)、ドライブ認定PSPathを取得するために$null
使用する必要があります。$drive.name + ":\" + $drive.CurrentLocation + "\" + "foo.txt"
クイックスタートC#スケルトン
これは、C#プロバイダー対応のコマンドレットのスケルトンです。FileSystemプロバイダーパスが渡されたことを確認するためのチェックが組み込まれています。私はこれをNuGet用にパッケージ化して、他のユーザーが適切に動作するプロバイダー対応のコマンドレットを作成できるようにしています。
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;
}
}
}
コマンドレット開発ガイドライン(Microsoft)
長期的に役立つ、より一般的なアドバイスを次に示します。http:
//msdn.microsoft.com/en-us/library/ms714657%28VS.85%29.aspx