web-dev-qa-db-ja.com

PowerShellコマンドレットを作成するときにパスを処理するにはどうすればよいですか?

C#コマンドレットを作成するときにパラメーターとしてファイルを受け取る適切な方法は何ですか?これまでのところ、文字列であるプロパティ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);
39
Matthew

これは驚くほど複雑な分野ですが、私はここでたくさんの経験を持っています。つまり、System.IO APIから直接win32パスを受け入れるコマンドレットがいくつかあり、これらは通常、-FilePathパラメーターを使用します。正常に動作する「powershelly」コマンドレットを作成する場合は、パイプライン入力を受け入れ、相対プロバイダーパスと絶対プロバイダーパスを操作するために、-Pathと-LiteralPathが必要です。これは私が少し前に書いたブログ投稿からの抜粋です:

PowerShellのパスは[最初は]理解するのが難しいです。PowerShellパス-またはPSPaths、Win32パスと混同しないでください-絶対形式では、2つの異なるフレーバーがあります。

  • プロバイダー認定:FileSystem::c:\temp\foo.txt
  • PSDrive認定:c:\temp\foo.txt

プロバイダー内部(解決されたSystem.Management.Automation.PathInfoProviderPathプロパティ–上記のプロバイダー修飾パスの::の右側の部分)とドライブ-について混乱するのは非常に簡単です。デフォルトのファイルシステムプロバイダードライブを見ると同じように見えるため、修飾パス。つまり、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は特に重要です。また、明らかにLiteralPathPathは相互に排他的なパラメータセットに属している必要があります。

相対パス

良い質問は、コマンドレットに渡される相対パスをどのように処理するかです。与えられているすべてのパスが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$nullになります)、$drive.name + ":\" + $drive.CurrentLocation + "\" + "foo.txt"を使用してドライブ修飾PSPath。

クイックスタート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(
            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;
        }
    }
}

コマンドレット開発ガイドライン(Microsoft)

長期的に役立つと思われる、より一般的なアドバイスを次に示します。 http://msdn.Microsoft.com/en-us/library/ms714657%28VS.85%29.aspx

68
x0n

これは、PowerShellスクリプトコマンドレットでPath入力を処理する方法です。

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

このメソッドは、次の方法で呼び出すことができます。

直接パスの場合:

My-Cmdlet .

ワイルドカード文字列を使用する場合:

My-Cmdlet *.txt

実際のファイルの場合:

My-Cmdlet .\PowerShell_transcript.20130714003415.txt

変数にファイルのセットがある場合:

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

または名前のみ:

My-Cmdlet -Path $x.Name

または、パイプラインを介してファイルのセットを一時停止します。

$x | My-Cmdlet
5
oɔɯǝɹ