Reputation: 44448
I'm trying to automate the creation of a server farm in PowerShell. Through manual creation I've got the following XML:
<webFarms>
<webFarm name="alwaysup" enabled="true">
<server address="alwaysup-blue" enabled="true">
<applicationRequestRouting httpPort="8001" />
</server>
<server address="alwaysup-green" enabled="true">
<applicationRequestRouting httpPort="8002" />
</server>
<applicationRequestRouting>
<healthCheck url="http://alwaysup/up.html" interval="00:00:05" responseMatch="up" />
</applicationRequestRouting>
</webFarm>
<applicationRequestRouting>
<hostAffinityProviderList>
<add name="Microsoft.Web.Arr.HostNameRoundRobin" />
</hostAffinityProviderList>
</applicationRequestRouting>
</webFarms>
Trying to do this via PS proves troublesome however: as far as I can tell there is no dedicated API to do this through (WebFarmSnapin is meant for an older version).
I have shifted my attention to IIS Administration Cmdlets but only got it half working.
The code that I have:
#####
# Overwriting the server farm
#####
Write-Host "Overwriting the server farm $($webFarmName)"
$webFarm = @{};
$webFarm["name"] = 'siteFarm'
Set-WebConfiguration "/webFarms" -Value $webFarm
#####
# Adding the servers
#####
Write-Host "Adding the servers"
$blueServer = @{}
$blueServer["address"] = 'site-blue'
$blueServer["applicationRequestRouting"] = @{}
$greenServer = @{}
$greenServer["address"] = 'site-green'
$greenServer["applicationRequestRouting"] = @{}
$servers = @($blueServer, $greenServer)
Add-WebConfiguration -Filter "/webFarms/webFarm[@name='siteFarm']" -Value $servers
#####
# Adding routing
#####
Write-Host "Adding the routing configurations"
$blueServerRouting = @{}
$blueServerRouting["httpPort"] = "8001"
Add-WebConfiguration -Filter "/webFarms/webFarm[@name='siteFarm']/server[@address='site-blue']" -Value $blueServerRouting
This generates
<webFarms>
<webFarm name="siteFarm">
<server address="site-blue" />
<server address="site-green" />
</webFarm>
<applicationRequestRouting>
<hostAffinityProviderList>
<add name="Microsoft.Web.Arr.HostNameRoundRobin" />
</hostAffinityProviderList>
</applicationRequestRouting>
</webFarms>
As you can see it's missing the port related to the routing. And I haven't even started with trying to add the healthcheck at this point.
What am I doing wrong? Is there some Cmdlet that I haven't found which makes this easier?
Related but without much of a useful answer (the PowerShell tab with generated code stays empty).
Upvotes: 15
Views: 3318
Reputation: 353
I've been working on this and I discovered that you can use Microsoft.Web.Administration
in Powershell to accomplish this.
Add-Type -AssemblyName "Microsoft.Web.Administration, Version=7.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"
$iisManager = New-Object Microsoft.Web.Administration.ServerManager
$applicationHostConfig = $IISManager.GetApplicationHostConfiguration()
# Get the WebFarms section. Section names are case-sensitive.
$webFarmSection = $ApplicationHostConfig.GetSection("webFarms")
$webFarms = $WebFarmSection.GetCollection()
# Create a new webfarm and assign a name.
$webFarm = $WebFarms.CreateElement("webFarm")
$webFarm.SetAttributeValue("name", "MyNewWebFarmName")
# Add it to the parent element
$webFarms.Add($webFarm)
# get Webfarm server collection
$servers = $webFarm.GetCollection()
# add server
$serverBlue = $servers.CreateElement("server")
$routingBlue = $serverBlue.GetChildElement("applicationRequestRouting")
$routingBlue.SetAttributeValue("httpPort", "8001")
$serverBlue.SetAttributeValue("address", "MyNewWebFarmName-blue")
$servers.Add($serverBlue)
# Save changes
$iisManager.CommitChanges()
There are two things that are important to mention:
Once you call $iisManager.CommitChanges()
then the object goes into read-only mode. You need to re-instantiate all objects before making any new changes.
If you decide to create a new server in your web farm, you'll run into a problem if you assign the server a name and then try to access its port settings. What you need to do is assign the port first and then assign the name and whether it's enabled/disabled. Otherwise, it'll throw a System.AccessViolationException: Attempted to read or write protected memory.
error.
Let me know how it goes!
Upvotes: 3
Reputation: 16867
It looks like you figured out how to do this by modifying the XML configuration file itself. Despite the feeling that this doesn't quite seem like the "right way", changing the config file directly is a perfectly valid solution. In essence, you created a template, and configuration templates provide a fast and readable approach to producing maintainable and repeatable configuration.
We can improve upon this approach by extracting the template text from the script into a separate text file. Then, scripts can read (or source) the template and swap out any placeholder values as needed. This is similar to how we separate concerns in a web application by decoupling our HTML templates from the code.
To more directly answer the question, let's take a look at how to do this using PowerShell and the IIS Administration API (you're correct—Web Farm Framework is no longer supported for recent versions if IIS and Windows). The original code in the question is a good start. We just need to distinguish between manipulating configuration collections (using *-WebConfiguration
cmdlets) and configuration items (using *-WebConfigurationProperty
cmdlets). Here's a script that will set the configuration values based on the example in the question:
$farmName = 'siteFarm'
Add-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' `
-Filter 'webFarms' `
-Name '.' `
-Value @{ name = $farmName; enabled = $true }
Add-WebConfiguration -PSPath 'MACHINE/WEBROOT/APPHOST' `
-Filter "webFarms/webFarm[@name='$farmName']" `
-Value @(
@{ address = 'site-blue'; enabled = $true },
@{ address = 'site-green'; enabled = $true }
)
Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' `
-Filter "webFarms/webFarm[@name='$farmName']/server[@address='site-blue']" `
-Name 'applicationRequestRouting' `
-Value @{ httpPort = 8001 }
Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' `
-Filter "webFarms/webFarm[@name='$farmName']/server[@address='site-green']" `
-Name 'applicationRequestRouting' `
-Value @{ httpPort = 8002 }
Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' `
-Filter "webFarms/webFarm[@name='$farmName']/applicationRequestRouting" `
-Name 'healthCheck' `
-Value @{
url = 'http://mySite/up.html'
interval = '00:00:05'
responseMatch = 'up'
}
Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' `
-Filter "webFarms/webFarm[@name='$farmName']/applicationRequestRouting" `
-Name 'protocol' `
-Value @{ reverseRewriteHostInResponseHeaders = $true }
Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' `
-Filter "webFarms/webFarm[@name='$farmName']/applicationRequestRouting/protocol" `
-Name 'cache' `
-Value @{ enabled = $false; queryStringHandling = 'NoCaching' }
This may be a bit more verbose than necessary, but I wanted to clearly illustrate the intention in each step. This implementation uses XPath queries for the -Filter
arguments to select the appropriate XML nodes. We could, perhaps, refactor the script to to reduce repetitive tasks, such as by defining an Add-FarmServer
function that takes a server name and port and then adds the appropriate directives. We may also need Remove-WebConfigurationLock
if we encounter locked configuration issues.
Whether we choose to use a template or a programmatic approach depends on the project and team preference. The API becomes much more attractive when we have many similar items to configure, such as if we have hundreds of servers to add to a web farm. On the other hand, templates are simple to understand don't require that other team members learn a new (and maybe somewhat confusing) API.
Upvotes: 7
Reputation: 44448
Seeing as nobody has come up with a solution: I've taken the very simple workaround of just editing the XML directly instead of going through an API.
The actual webfarm functionality I haven't got to work yet but it seems like all the parts are there at least.
An example of manipulating the XML can be this:
$configPath = "C:\Windows\System32\inetsrv\config\applicationHost.config"
$configXml = [xml] (type $configPath)
[xml] $webFarmXml = @"
<webFarms>
<webFarm name="siteFarm" enabled="true">
<server address="site-blue" enabled="true">
<applicationRequestRouting httpPort="$bluePort" />
</server>
<server address="site-green" enabled="true">
<applicationRequestRouting httpPort="$greenPort" />
</server>
<applicationRequestRouting>
<healthCheck url="http://mySite/up.html" interval="00:00:05" responseMatch="up" />
<protocol reverseRewriteHostInResponseHeaders="true">
<cache enabled="false" queryStringHandling="NoCaching" />
</protocol>
</applicationRequestRouting>
</webFarm>
<applicationRequestRouting>
<hostAffinityProviderList>
<add name="Microsoft.Web.Arr.HostNameRoundRobin" />
</hostAffinityProviderList>
</applicationRequestRouting>
</webFarms>
"@
$configXml.configuration.AppendChild($configXml.ImportNode($webFarmXml.webFarms, $true))
$configXml.Save($configPath)
Upvotes: 1