Powershell新建Web绑定管道时,名称属性出现问题

3
我在使用New-WebBinding命令时遇到了问题。我有一个对象,定义了5个属性:名称、协议、端口、IP地址和主机头(这5个属性都支持New-WebBinding命令作为管道输入:ValueByPropertyName)。然而,当您将此对象作为管道输入时,它仍然要求提交一个名称。如果您想复制此问题,请使用以下快速测试函数。如果您在提示符处按回车键,则会成功处理对象并添加绑定。但是,提示符本身会导致非交互式脚本出现问题。
我已经在PS v3和PS v4中进行了测试。
我相信我做得很正确,但想确定是否有我可能忽略的内容。目前,我只是在foreach循环中迭代我的对象集合,这种方法没有这个问题,但我想知道这是否是我应该报告的错误。
function Test-WebBinding{
   [CmdletBinding()]
   Param()

   $testBindingCol = @()

   $testBinding1 = New-Object System.Object
   $testBinding1 | Add-Member -MemberType NoteProperty -Name Name -Value 'Default Web Site'
   $testBinding1 | Add-Member -MemberType NoteProperty -Name Protocol -Value 'https'
   $testBinding1 | Add-Member -MemberType NoteProperty -Name Port -Value '4000'
   $testBinding1 | Add-Member -MemberType NoteProperty -Name IPAddress -Value '*'
   $testBinding1 | Add-Member -MemberType NoteProperty -Name HostHeader -Value 'Test4000'
   $testBindingCol += $testBinding1

   $testBinding2 = New-Object System.Object
   $testBinding2 | Add-Member -MemberType NoteProperty -Name Name -Value 'Default Web Site'
   $testBinding2 | Add-Member -MemberType NoteProperty -Name Protocol -Value 'http'
   $testBinding2 | Add-Member -MemberType NoteProperty -Name Port -Value '4001'
   $testBinding2 | Add-Member -MemberType NoteProperty -Name IPAddress -Value '*'
   $testBinding2 | Add-Member -MemberType NoteProperty -Name HostHeader -Value 'Test4001'
   $testBindingCol += $testBinding2

   $testBindingCol | New-WebBinding
}

期待看到这个问题的答案。我也尝试了PSObjectPSCustomObject的不同变化,但没有成功。好问题。 - Kev
目前看来,Microsoft Connect 并没有接收任何版本的 Powershell 的错误报告。因此,我会将这个问题留下作为最有可能的错误,并且唯一的解决方法是通过 foreach 迭代你的对象。我还没有使用版本 5,不知道最新版本是否已经解决了这个问题。 - Paolis
这在v5中的行为相同。我已经反汇编了Microsoft.IIs.PowerShell.Provider程序集,其中包含New-WebBinding cmdlet,并且我们感兴趣的属性已经正确地标记为[Parameter(ValueFromPipelineByPropertyName = true)] - Kev
1
这绝对是个bug。一个解决方法是将当前位置更改为某个站点(cd IIS:\Sites\SomeSite),实际上无论更改为哪个站点都没有关系。 - user4003407
1个回答

5

PetSerAl在他上面的评论中提出了正确的想法:

一个解决方法是将当前位置更改为某个站点(cd IIS:\Sites\SomeSite),无论到哪里都没有关系

这确实可以工作,但为什么不能从普通的文件系统提示符中工作呢?

为了发现New-WebBinding为什么会表现出这种方式,我加载了包含此和其他WebAdministration cmdlet的Microsoft.IIS.PowerShell.Provider程序集到dotPeek中。该程序集位于GAC中,因此您可以告诉dotPeek“从GAC打开”。

加载后,我们感兴趣的类称为NewWebBindingCommand

经过粗略检查,我们可以看到所有参数属性都带有[Parameter(ValueFromPipelineByPropertyName = true)]属性修饰符,所以这是一个好的开始,使用具有匹配属性名称的对象数组应该能够工作:

enter image description here

NewWebBindingCommand 最终继承自 System.Management.Automation.Cmdlet,在这个实例中覆盖了 BeginProcessing 方法。如果被覆盖,PowerShell 会调用 BeginProcessing 并提供一次性的预处理功能来执行 cmdlet。
重要的是要理解,在将任何管道提供的命名参数处理并绑定到 cmdlet 的属性之前,会先调用 cmdlet 的 BeginProcessing 重写(请参阅:Cmdlet Processing Lifecycle (MSDN))。
我们的 New-WebBinding cmdlet 实现的 BeginProcessing 如下所示:
protected override void BeginProcessing()
{
  base.BeginProcessing();
  if (!string.IsNullOrEmpty(this.siteName))
    return;
  this.siteName = this.GetSiteName("Name");
}

this.siteNameName 属性的私有成员值,该属性将绑定到 -Name 参数。当我们到达上面的 if(...) 语句时,`this.siteName` 尚未绑定(为 null),因此会进入以下代码块:

this.siteName = this.GetSiteName("Name");

调用GetSiteName()方法时,它会向上调用到命令的直接基类HelperCommand,该基类提供了许多对于许多不同的WebAdministration cmdlet都有用的“帮助器”方法。 HelperCommand.GetSiteName(string prompt)方法如下所示:
protected string GetSiteName(string prompt)
{
  PathInfo pathInfo = this.SessionState.PSVariable.Get("PWD").Value as PathInfo;
  if (pathInfo != null && pathInfo.Provider.Name.Equals("WebAdministration", StringComparison.OrdinalIgnoreCase))
  {
    string[] strArray = pathInfo.Path.Split('\\');
    if (strArray.Length == 3 && strArray[1].Equals("sites", StringComparison.OrdinalIgnoreCase))
      return strArray[2];
  }
  if (!string.IsNullOrEmpty(prompt))
    return this.PromptForParameter<string>(prompt);
  return (string) null;
}

为了学习这个问题,我创建了自己的PowerShell cmdlet(称为Kevulator,抱歉),并引入了New-WebBinding cmdlet的BeginProcessing()代码以及New-WebBinding基类帮助方法GetSiteName()中的代码。
下面是在使用VS2015附加到一个PowerShell会话并将绑定管道传递到New-Kevulator时,在GetSiteName中断的屏幕截图:

enter image description here

顶部的箭头说明我们的Name属性由siteName支持,尚未绑定,仍然为null(正如上面提到的会导致GetSiteName被执行)。我们刚刚跨越了这行上的断点。
PathInfo pathInfo = 
        this.SessionState.PSVariable.Get("PWD").Value as PathInfo;

...它决定了我们所处的路径提供程序的类型。在这种情况下,我们在一个普通的文件系统 C:\> 提示符上,因此提供程序名称将是FileSystem。我用第二个箭头突出了这一点。

如果我们Import-Module WebAdministration并且CD IIS:然后重新运行并再次中断,您会发现路径提供程序已更改为负责处理IIS:>及其后面内容的WebAdministration

enter image description here

如果 pathInfo 名称不等于 WebAdministration,即我们不在一个 IIS: 提示符下,则代码会跳过并在命令行提示输入 Name 参数,就像您经历的那样。
如果 pathInfo 值是 WebAdministration,则会发生以下两种情况之一:
如果路径为 IIS:IIS:\Sites,则代码会跳过并提示输入 Name 参数。
如果路径为 IIS:\Sites\SomeSiteName,则返回 SomeSiteName 并有效地成为 -Name 参数值,然后从 GetSiteName() 函数回到 BeginProcessing

最后一个行为非常有用,因为如果您的当前路径是 IIS:\Sites\MySite,那么您可以将一个绑定数组导入该站点,但不指定 Name 参数。或者,如果您在管道化对象的数组中提供了一个 Name 属性,则这些属性将覆盖从您的 IIS:\Sites\MySite 上下文中获取的默认站点名称。

所以,回到我们的代码,一旦 BeginProcessing 运行完毕,我们的管道命名参数现在实际上已经绑定,然后调用 ProcessRecord 方法,这是 New-WebBinding 必须执行的主要工作。

简而言之:

除非您将当前工作目录更改为 IIS 网站(例如 cd iis:\Sites\MySite),否则 New-WebBinding 不会绑定管道参数。


非常好的分解和解释。感谢您的努力。 - Paolis
@Paolis 不用谢,这是一个太好的机会不能错过 :) - Kev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接