编写 PowerShell Cmdlet 时如何处理路径?

问题描述 投票:0回答:1

编写 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);
c# powershell parameters powershell-cmdlet
1个回答
75
投票

这是一个极其复杂的领域,但我在这里有丰富的经验。简而言之,有一些 cmdlet 直接从 System.IO API 接受 win32 路径,并且这些 cmdlet 通常使用 -FilePath 参数。如果您想编写一个行为良好的“powershelly”cmdlet,则需要 -Path 和 -LiteralPath,以接受管道输入并使用相对和绝对提供程序路径。这是我不久前写的博客文章的摘录:

PowerShell 中的路径[一开始很难理解。] PowerShell 路径 - 或 PSPaths,不要与 Win32 路径混淆 - 在其绝对形式中,它们有两种不同的风格:

  • 提供商资格:
    FileSystem::c:\temp\foo.txt
  • PSDrive 合格:
    c:\temp\foo.txt

很容易混淆提供者内部(已解析的

ProviderPath
System.Management.Automation.PathInfo
属性 – 上面提供者限定路径的
::
右侧的部分)和驱动器限定路径,因为它们看起来像如果您查看默认的文件系统提供程序驱动器,情况也是如此。也就是说,PSDrive 与本机后备存储 Windows 文件系统 (C) 具有相同的名称 (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
    参数(它将解析通配符/全局变量)
  • 始终假设您正在接收 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;
        }
    }
}

Cmdlet 开发指南 (Microsoft)

以下是一些更通用的建议,从长远来看应该对您有所帮助: https://learn.microsoft.com/en-us/powershell/scripting/developer/cmdlet/strongly-encouraging-development-guidelines?view=powershell-7.4#support-windows-powershell-paths

© www.soinside.com 2019 - 2024. All rights reserved.