Подтвердить что ты не робот

Как я могу работать с контурами при написании командлета PowerShell?

Каков правильный способ получения файла в качестве параметра при написании командлета С#? До сих пор у меня просто есть свойство LiteralPath (совпадающее с соглашением об именах параметров), которое является строкой. Это проблема, потому что вы просто получаете то, что набрано на консоль; который может быть полным путем или может быть относительным путем.

Использование Path.GetFullPath(string) не работает. Он думает, что я нахожусь в ~, я нет. Такая же проблема возникает, если я изменяю свойство из строки в FileInfo.

EDIT: для всех, кого это интересует, это обходное решение работает для меня:

    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);
4b9b3361

Ответ 1

Это удивительно сложная область, но у меня здесь много опыта. Короче говоря, есть некоторые командлеты, которые принимают пути win32 прямо из API System.IO, и они обычно используют параметр -FilePath. Если вы хотите написать хорошо выполненный командлет "powershelly", вам нужно -Path и -LiteralPath, чтобы принять ввод конвейера и работать с относительными и абсолютными путями провайдеров. Вот выдержка из сообщения в блоге, которое я написал некоторое время назад:

Пути в PowerShell трудно понять [сначала.] PowerShell Paths - или PSPaths, чтобы не путать с путями Win32 - в их абсолютных формах они бывают двух отличных ароматов:

  • Провайдер: 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.

Итак, ваши цели для написания обобщенного дружественного к провайдеру Cmdlet (или расширенной функции), который принимает пути:

  • Определить параметр пути LiteralPath с псевдонимом PSPath
  • Определите параметр Path (который будет разрешать подстановочные знаки /glob )
  • Всегда предполагайте, что вы получаете PSPaths, а не собственные пути поставщика (например, пути Win32).

Особенно важна точка с номером три. Кроме того, очевидно, что LiteralPath и Path должны принадлежать во взаимоисключающих наборах параметров.

Относительные пути

Хороший вопрос: как мы относимся к относительным путям, передаваемым в командлет. Поскольку вы должны предполагать, что все пути, предоставленные вам, - это PSPaths, давайте посмотрим, что делает приведенная ниже Cmdlet:

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

Команда должна предполагать, что foo.txt находится в текущем диске, поэтому это должно быть немедленно разрешено в блоке ProcessRecord или EndProcessing, например (используя здесь скрипт для демонстрации):

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

Теперь вы все, что вам нужно, чтобы воссоздать две абсолютные формы PSPaths, и у вас также есть собственный абсолютный ProviderPath. Чтобы создать PSPath-провайдера для foo.txt, используйте $provider.Name + "::" + $providerPath. Если $drive не $null (ваше текущее местоположение может быть квалифицированным провайдером, в этом случае $drive будет $null), вы должны использовать $drive.name + ":\" + $drive.CurrentLocation + "\" + "foo.txt", чтобы получить PSPath с сертификатом на диске.

Скелет быстрого запуска С#

Здесь находится скелет командлета, поддерживающего С# поставщика, чтобы вы начали. Он имеет встроенные проверки, чтобы гарантировать, что ему был предоставлен путь провайдера 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

Ответ 2

Таким образом вы можете обрабатывать ввод Path и LiteralPath в командлете PowerShell script:

function My-Cmdlet {
    [CmdletBinding(DefaultParameterSetName = "Path")]
    Param(
        [Parameter(Position = 0, ParameterSetName = "Path", Mandatory = $true)]
        [string[]] $Path,

        [Parameter(ParameterSetName = "LiteralPath", Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias("FullName")]
        [string[]] $LiteralPath
    )

    Begin {
    }

    Process {
        # combine Path and LiteralPath
        # ignore empty values
        # resolve the path
        # Convert it to remove provider path
        foreach($curPath in ($LiteralPath + $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
                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