Reputation: 150
I'm encountering an issue with New-WebBinding when piping in an object. I have an object that defines 5 properties: Name, Protocol, Port, IPAddress and HostHeader (all 5 are supported in the New-WebBinding cmdlet as Accept Pipeline input: ValueByPropertyName). However, when you pipe in this object, it still requests a Name: to be submitted. Here is a quick test function if you'd like to duplicate the issue. If you hit enter at the prompt, it successfully processes the objects, adding the bindings. But the prompt itself breaks it as a non-interactive script.
I've tested this with both PS v3 and PS v4.
I'm pretty sure I'm doing this all correctly but wanted to make sure there wasn't something I might be overlooking. For now I'm just iterating through my object collection in a foreach loop which does not have this issue but would like to see if this is a bug I should report.
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
}
Upvotes: 3
Views: 748
Reputation: 119846
PetSerAl is onto the right idea with his comment above:
One workaround would be to change current location to some site (
cd IIS:\Sites\SomeSite
), it does not really matter to which
That does actually work, but why doesn't it work from the normal file system prompt?
To discover why New-WebBinding
behaves this way I loaded the Microsoft.IIS.PowerShell.Provider
assembly containing this and other WebAdministration
cmdlets into dotPeek. The assembly lives in the GAC so you tell dotPeek to "Open from GAC".
When loaded, the class we're interested in is called NewWebBindingCommand
.
Upon a cursory inspection we can see that all of the parameter properties are decorated with the [Parameter(ValueFromPipelineByPropertyName = true)]
attribute so that's a good start, piping an array of objects with matching property names ought to work:
NewWebBindingCommand
ultimately inherits from System.Management.Automation.Cmdlet
and in this instance is overriding the BeginProcessing
method. If overridden, BeginProcessing
is called by PowerShell and "Provides a one-time, preprocessing functionality for the cmdlet."
It is important to understand that a cmdlet's BeginProcessing
override is called before any pipeline fed named parameters are processed and bound to the cmdlet's properties (see: Cmdlet Processing Lifecycle (MSDN)) .
Our New-WebBinding
cmdlet's implementation of BeginProcessing
looks like:
protected override void BeginProcessing()
{
base.BeginProcessing();
if (!string.IsNullOrEmpty(this.siteName))
return;
this.siteName = this.GetSiteName("Name");
}
this.siteName
is the private member value for the Name
property which would be bound to the -Name
parameter. When we arrive at the if(...)
statement above `this.siteName isn't yet bound (it's null) and so falls through to:
this.siteName = this.GetSiteName("Name");
The call to GetSiteName()
calls up to the cmdlet's immediate base class HelperCommand
which provides a number of "helper" methods that are useful to many different WebAdministration
cmdlets.
HelperCommand.GetSiteName(string prompt)
looks like this:
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;
}
For the purpose of learning about this issue I created my own PowerShell cmdlet (called the Kevulator
, sorry) and pulled in the New-WebBinding
cmdlet's BeginProcessing()
code and the code from New-WebBinding
's base class helper method GetSiteName()
.
Here's a screenshot of breaking inside GetSiteName
in VS2015 when attached to a PowerShell session piping in your bindings to New-Kevulator
:
The top arrow demonstrates that our Name
property backed by siteName
hasn't yet been bound and is still null
(which as a mentioned above causes GetSiteName
to be executed). We've also just stepped past a break point on this line:
PathInfo pathInfo =
this.SessionState.PSVariable.Get("PWD").Value as PathInfo;
...which determines what kind of path provider we're sitting at. In this case we're on a normal filesystem C:\>
prompt so the provider name will be FileSystem
. I've highlighted this with the second arrow.
If we Import-Module WebAdministration
and CD IIS:
then re-run and break again you can see that the path provider has changed to WebAdministration
which is responsible for handling life inside IIS:>
and beyond:
If the pathInfo
name is not equal to WebAdministration
, i.e. we're not at an IIS:
prompt, then the code falls through and will prompt for the Name
parameter at the command line, just as you've experienced.
If the pathInfo
value is WebAdministration
then one of two things will happen:
If the path is IIS:
or IIS:\Sites
the code falls through and issues a prompt for the Name
parameter.
If the path is IIS:\Sites\SomeSiteName
then SomeSiteName
is returned and effectively becomes the -Name
parameter value and we bail out from the GetSiteName()
function back to BeginProcessing
.
That last behaviour is useful because if your current path is say IIS:\Sites\MySite
then you can pipe in an array of bindings for that site but without specifying the Name
parameter. Alternatively if you do provide a Name
property in your piped array of objects then these will override the default site name picked up from your IIS:\Sites\MySite
context.
So, getting back to our code, once BeginProcessing
has run its course our pipeline named parameters are now actually bound and then the ProcessRecord
method is called, which is the meat and potatoes work that New-WebBinding
has to perform.
TLDR:
New-WebBinding
won't bind pipelined parameters unless you change your current working directory to an IIS website, e.g, cd iis:\Sites\MySite
.
Upvotes: 5