无法使用FileInfo通过管道从二进制模块获取内容



我很难将文件传送到Get-Content(PS 5.1版本(。

模块代码:

[Cmdlet(VerbsDiagnostic.Test, "FilePiping")]
public class PSTestFilePiping : PSCmdlet
{
[Parameter(Position = 0, Mandatory = true)]
public string FileName { get; set; }
protected override void ProcessRecord()
{
WriteObject(new System.IO.FileInfo(FileName));
}
}

命令:

$x = Test-FilePiping .readme.txt

$y = gci .readme.txt

(为了简单起见,我使用的是相对路径(

根据GetType():,$x$y都是FileInfo对象

IsPublic IsSerial Name                                     BaseType
True     True     FileInfo                                 System.IO.FileSystemInfo

但我做不到:

$x | gc

The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.

但我可以:

$y | gc

这让我很困惑。

我哪里错了?

除了@santiago squarzon的有用答案外,这里还有两件事值得注意:

  • Get-ItemGet-ChildItem这样的PowerShell命令不仅仅是为FileSystem生成的,因此返回相关提供程序的信息,PSPath这样的值就是从这里来的
  • new FileInfo()为目标的相对路径可能找不到您期望的文件,因此我们应该获取完整路径以备不时之需

这可以在cmdlet中实现,以便它返回Get-Content所需的PSPath属性。

样品:

dotnet new classlib -o PSSample -n PSSample
cd PSSample
dotnet add package PowerShellStandard.Library --version 5.1.0
code . # I am using VSC - open your editor in the directory.
# Set target framework to netstandard2.0 in .csproj
# Copy and paste below in to Class1.cs
dotnet restore
dotnet build
ipmo .binDebugnetstandard2.0PSSample.dll
Test-FilePiping -FileName .Class1.cs | FL *
Test-FilePiping -FileName .Class1.cs | Get-Content

Cmdlet类:

using System;
using System.IO;
using System.Linq;
using System.Management.Automation;
namespace PSSample
{
[Cmdlet(VerbsDiagnostic.Test, "FilePiping")]
public class PSTestFilePiping : PSCmdlet
{
[Parameter(Position = 0, Mandatory = true)]
public string FileName { get; set; }
protected override void ProcessRecord()
{
string filePath = GetRelativePath(FileName);
var item = InvokeProvider.Item.Get(filePath);
WriteObject(item, true);    // true enumerates the collection, because InvokeProvider.Item.Get() returns a collection.
}
protected string GetRelativePath(string path)
{
string currentDir = GetVariableValue("PWD").ToString();
if (Path.IsPathRooted(path) == true || path.StartsWith("\", StringComparison.CurrentCulture))
{
// Nothing to see here.
}
else
{
if (path == ".")
{
path = currentDir;
}
else if (path.StartsWith("..", StringComparison.CurrentCulture))
{
path = Path.Combine(
string.Join("\",
currentDir.Split('\').Take(currentDir.Split('\').Count() - path.Split('\').Count(p => p == "..")).ToArray()
),
string.Join("\", path.Split('\').Where(f => f != "..").ToArray()));
}
else if (path.StartsWith(".", StringComparison.CurrentCulture))
{
path = Path.Combine(currentDir, path.Substring(2));
}
else
{
path = Path.Combine(currentDir, path);
}
}
return path;
}
}
}

输出:

PS C:CodePSSample> Test-FilePiping -FileName .Class1.cs | FL *

PSPath            : Microsoft.PowerShell.CoreFileSystem::C:CodePSSampleClass1.cs
PSParentPath      : Microsoft.PowerShell.CoreFileSystem::C:CodePSSample
PSChildName       : Class1.cs
PSDrive           : C
PSProvider        : Microsoft.PowerShell.CoreFileSystem
PSIsContainer     : False
Mode              : -a----
VersionInfo       : File:             C:CodePSSampleClass1.cs
InternalName:
OriginalFilename:
FileVersion:
FileDescription:
Product:
ProductVersion:
Debug:            False
Patched:          False
PreRelease:       False
PrivateBuild:     False
SpecialBuild:     False
Language:
BaseName          : Class1
Target            : {}
LinkType          :
Name              : Class1.cs
Length            : 1947
DirectoryName     : C:CodePSSample
Directory         : C:CodePSSample
IsReadOnly        : False
Exists            : True
FullName          : C:CodePSSampleClass1.cs
Extension         : .cs
CreationTime      : 02/05/2021 13:03:22
CreationTimeUtc   : 02/05/2021 12:03:22
LastAccessTime    : 02/05/2021 13:04:13
LastAccessTimeUtc : 02/05/2021 12:04:13
LastWriteTime     : 02/05/2021 13:04:00
LastWriteTimeUtc  : 02/05/2021 12:04:00
Attributes        : Archive

PS C:CodePSSample> Test-FilePiping -FileName .Class1.cs | Get-Content
using System;
using System.IO;
using System.Linq;
using System.Management.Automation;
namespace PSSample
{
[Cmdlet(VerbsDiagnostic.Test, "FilePiping")]
public class PSTestFilePiping : PSCmdlet
{
[Parameter(Position = 0, Mandatory = true)]
public string FileName { get; set; }
protected override void ProcessRecord()
{
string filePath = GetRelativePath(FileName);
var item = InvokeProvider.Item.Get(filePath);
WriteObject(item, true);    // true enumerates the collection, because InvokeProvider.Item.Get() returns a collection.
}
protected string GetRelativePath(string path)
{
string currentDir = GetVariableValue("PWD").ToString();
if (Path.IsPathRooted(path) == true || path.StartsWith("\", StringComparison.CurrentCulture))
{
// Nothing to see here.
}
else
{
if (path == ".")
{
path = currentDir;
}
else if (path.StartsWith("..", StringComparison.CurrentCulture))
{
path = Path.Combine(
string.Join("\",
currentDir.Split('\').Take(currentDir.Split('\').Count() - path.Split('\').Count(p => p == "..")).ToArray()
),
string.Join("\", path.Split('\').Where(f => f != "..").ToArray()));
}
else if (path.StartsWith(".", StringComparison.CurrentCulture))
{
path = Path.Combine(currentDir, path.Substring(2));
}
else
{
path = Path.Combine(currentDir, path);
}
}
return path;
}
}
}

用一些背景信息补充现有的有用答案:

是PowerShellFileSystem提供程序使用PowerShell的ETS(扩展类型系统(,用附加属性(如PSPath(以及其他几个[1]((装饰Get-ChildItemGet-Item发出的System.IO.FileSystemInfo实例(System.IO.FileInfoSystem.IO.DirectoryInfo(。

管道输入对象的.PSPath属性值是绑定到cmdlet(如Get-Content(的-LiteralPath参数的属性值,因为所述参数是用ValueFromPipelineByPropertyName属性声明的,并且LiteralPath[Alias("PSPath")]属性修饰;即PSPath别名(LiteralPath的另一名称(。

在撰写本文时,PowerShell提供程序将这些ETS属性添加为实例属性,这就是直接构建的System.IO.FileInfo不具有这些属性的原因,从而导致出现您看到的症状(.PSPath属性的缺失阻止了绑定到-LiteralPath参数(。

如果提供者使用类型级别ETS属性(与.NET类型本身相关,而不是与它的特定实例相关(,这个问题就会消失,因为无论实例是如何构造的,这些属性都会出现。

有关讨论,请参阅GitHub第4347期。


[1]添加属性的完整列表为:PSPathPSParentPathPSChildNamePSDrivePSProviderPSIsContainerModeBaseNameTargetLinkType

希望这能帮助您识别错误:

PS /> 'Hello world!' > test.txt
PS /> $reader = [System.IO.FileInfo]::new('test.txt')

如果我尝试将此对象直接管道传输到Get-Content,它将抛出与您得到的错误相同的错误:

使用此:

PS /> $reader | Get-Content

或者这个:

PS /> $reader.FullName | Get-Content

结果:

Get-Content: The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.

这是因为[System.IO.FileInfo]返回的属性都与Get-Content:接受为ValueFromPipelineByProperyName的参数不匹配

PS /> $reader | gm -MemberType Property
TypeName: System.IO.FileInfo
Name              MemberType Definition
----              ---------- ----------
Attributes        Property   System.IO.FileAttributes Attributes {get;set;}
CreationTime      Property   datetime CreationTime {get;set;}
CreationTimeUtc   Property   datetime CreationTimeUtc {get;set;}
Directory         Property   System.IO.DirectoryInfo Directory {get;}
DirectoryName     Property   string DirectoryName {get;}
Exists            Property   bool Exists {get;}
Extension         Property   string Extension {get;}
FullName          Property   string FullName {get;}
IsReadOnly        Property   bool IsReadOnly {get;set;}
LastAccessTime    Property   datetime LastAccessTime {get;set;}
LastAccessTimeUtc Property   datetime LastAccessTimeUtc {get;set;}
LastWriteTime     Property   datetime LastWriteTime {get;set;}
LastWriteTimeUtc  Property   datetime LastWriteTimeUtc {get;set;}
Length            Property   long Length {get;}
Name              Property   string Name {get;}

然而,如果我们创建一个具有我在评论中提到的任何属性的对象,Get-Content将接受它作为输入并读取它而不会出现任何问题:

PS /> [PSCustomObject]@{ Path = $reader.FullName } | Get-Content
Hello world!
PS /> [PSCustomObject]@{ LiteralPath = $reader.FullName } | Get-Content
Hello world!
PS /> [PSCustomObject]@{ PSPath = $reader.FullName } | Get-Content
Hello world!

Get-ChildItem工作正常,因为返回的对象具有PSPath属性

识别哪些参数被接受为ValueFromPipelineByProperyName的最佳方法是依赖MS文档:

-LiteralPath
类型String[]
别名PSPath,LP
职位已命名
默认值None
接受管道输入
接受通配符False

最新更新