A solução
- Criação do novo site
- Geração da configuração do novo site com base na configuração do site base
- Importação da configuração do novo site
- Iisweb.vbs: permite efectuar todas as operações de gestão de um site (criação, remoção, etc). Para mais detalhes, cliquem aqui.
- iiscnfg.vbs: permite efectuar as operações de gestão da configuração de um site (exportação e importação de configuração, etc). Para mais detalhes, cliquem aqui.
Criação do novo site
- Endereço IP – permite a definição de um endereço IP como modo de acesso do site. Deste modo, o porto 80 pode ser usado por mais do que um site desde que sejam usados endereços IP diferentes.
- Porto – permite a definição de um porto como modo de acesso do site. Deste modo, tem que ser usado um porto diferente para cada site mas permite o uso do mesmo endereço IP.
- Host Header – um host header, tal como o nome indica, é um cabeçalho definido ao nível do site e permite que diversos sites utilizem o mesmo porto para o mesmo endereço IP. Exemplos de host headers são “www.create.pt” ou “blogit.create.pt” (o URL que se pretende para o acesso externo ao site sem “http//”). Um pedido HTTP chega ao servidor web e o utilizador é redireccionado para o site correcto com base no URL. Como nota final, de referir que usando host headers, é necessário criar uma entrada no DNS por cada site criado a apontar para o endereço IP externo do servidor web.
//constantes usadas ao longo dos exemplos
private const string IIS_WEB_COMMAND = @"C:\WINDOWS\system32\iisWeb.vbs";
private const string IIS_CONFIG_COMMAND = @"C:\WINDOWS\system32\iisCnfg.vbs";
private const string WINDOWS_COMMAND = "cmd";
private const string SITEPATH_REGULAR_EXPRESSION = @"W3SVC/\d*";
private int CreateSite(string webSiteHomeDirectory, string siteName, string ip, string port, string hostHeader)
{
Process process = new Process();
process.EnableRaisingEvents = false;
//criar site mas não arrancá-lo
//iisweb /create <directoria> <nome> /donstart
StringBuilder commandArguments = new StringBuilder(String.Format(@" /create {0} {1} /dontstart", webSiteHomeDirectory, siteName));
//modo de acesso por endereço IP
if (ip != null && ip.Trim().Length > 0)
commandArguments.Append(String.Format(" /i {0}", ip));
//modo de acesso por porto
if (port != null && port.Trim().Length > 0)
commandArguments.Append(String.Format(" /b {0}", port));
//modo de acesso por host header
if (hostHeader != null && hostHeader.Trim().Length > 0)
commandArguments.Append(String.Format(" /d {0}", hostHeader));
//assignar o script a correr
process.StartInfo.FileName = IIS_WEB_COMMAND;
process.StartInfo.Arguments = commandArguments.ToString();
/iniciar o processo
process.Start();
process.WaitForExit();
//retornar código de execução do processo. Retorna 0 se tudo correr bem.
return process.ExitCode;
}
Ao longo dos exemplos, é utilizada a classe System.Diagnostics.Process para efectuar a execução dos scripts do IIS. Para mais detalhes sobre esta classe, cliquem aqui.
Geração da configuração do novo site com base na configuração do site base
Neste passo, é lida o ficheiro de metabase do site base e gerado a partir deste, o ficheiro de configuração do novo site. As alterações a efectuar à configuração base são compostos tipicamente por:
-
Alteração do ID do site para o ID do novo site
-
Alteração da directoria do site (e das directorias virtuais se existirem) para as do novo site
-
Alterar o nome do site para o do novo site
-
Alterar o host header para o do novo site
private string CreateSiteTemplateXml(string xmlConfigPath, string masterWebID, string createdWebID, string siteName, string hostHeader)
{
XmlDocument xmlDocument = new XmlDocument();
//ler ficheiro de metabase do site base
xmlDocument.Load(xmlConfigPath);
//seleccionar elemento xml IISWebServer da metabase
XmlNodeList xmlNodeList = xmlDocument.DocumentElement.SelectNodes(“./MBProperty/IIsWebServer”);
//substituir host header
foreach(XmlNode xmlNode in xmlNodeList)
{
xmlAttribute = XmlHelper.GetAttribute(xmlNode.Attributes, “ServerBindings”);
string serverBinding = xmlAttribute.Value;
string [] serverBindings = serverBinding.Split(':');
string oldHostHeader = serverBindings[serverBindings.Length – 1];
xmlAttribute.Value = xmlAttribute.Value.Replace(oldHostHeader, hostHeader);
}
//substituir nome site
foreach(XmlNode xmlNode in xmlNodeList)
{
xmlAttribute = XmlHelper.GetAttribute(xmlNode.Attributes, “ServerComment”);
xmlAttribute.Value = xmlAttribute.Value.Replace(xmlAttribute.Value, siteName);
}
//seleccionar elemento xml IIsWebVirtualDir da metabase
xmlNodeList = xmlDocument.DocumentElement.SelectNodes(“./MBProperty/IIsWebVirtualDir”);
foreach(XmlNode xmlNode in xmlNodeList)
{
//substituir atributo AppRoot com o id do novo site
xmlAttribute = XmlHelper.GetAttribute(xmlNode.Attributes, “AppRoot”);
xmlAttribute.Value = xmlAttribute.Value.Replace(masterWebID, createdWebID);
}
//substituir directorias do site (existe um elemento IIsWebVirtualDir para a root e para cada directoria virtual). Este exemplo assume que os nomes das directorias do site irão conter o id do site no IIS, sendo substituido o id do site base pelo id do novo site. Ex: C:\inetpub\wwwroot\site<idsitebase> -> C:\inetpub\wwwroot\site<idsitenovo>
foreach(XmlNode xmlNode in xmlNodeList)
{
xmlAttribute = XmlHelper.GetAttribute(xmlNode.Attributes, “Path”);
xmlAttribute.Value = xmlAttribute.Value.Replace(masterWebID, createdWebID);
}
//gerar ficheiro metabase novo site
string newPath = String.Format("{0}{1}.xml", xmlConfigPath.Replace(".xml", String.Empty), createdWebID.Replace(Constants.IISWebSiteIdentifierPrefix, String.Empty));
xmlDocument.Save(newPath);
//retornar caminho para ficheiro metabase do novo site
return newPath;
}
Importação da configuração do novo site
Neste passo, é efectuada a importação da configuração criada no passo anterior. O seguinte método e responsável por esta operação.
private int ImportSiteConfiguration(string xmlConfigPath, string masterWebID, string createdWebID)
{
Process process = new Process();
process.EnableRaisingEvents=false;
string commandArguments = String.Format(@" /import /f ""{0}"" /sp /LM/{1}/root /dp /LM/{2}/root /children", xmlConfigPath, masterWebID, createdWebID);
process.StartInfo.FileName = IIS_CONFIG_COMMAND;
process.StartInfo.Arguments = commandArguments;
process.Start();
process.WaitForExit();
return process.ExitCode;
}
O processo de criação do site
O método seguinte efectua a chamada a cada uma dos métodos anteriores e alguns métodos auxiliares para efectuar o processo de criação do site.
public void CreateNewWebSite(string webSiteHomeDirectory, string webSiteBackOfficeFolder, string siteName,
string ip, string port, string hostHeader)
{
//criar site
int createSiteResult = CreateSite(webSiteHomeDirectory, siteName, null, null, hostHeader);
if (createSiteResult == 0) //criação site ok
{
//obter id do novo site
string createdSiteID = GetSiteID(siteName);
//gerar ficheiro configuração novo site
string xmlConfigPath = CreateSiteTemplateXml(webSiteHomeDirectory, webSiteBackOfficeFolder, Settings.MasterWebSiteXmlConfigurationPath,
Settings.MasterWebSiteID, createdSiteID, siteName, hostHeader);
//importar configuraçao novo site
int importSiteResult = ImportSiteConfiguration(xmlConfigPath, Settings.MasterWebSiteID, createdSiteID);
if (importSiteResult == 0) //importação ok
StartSite(siteName);
}
}
Outros Métodos Utilizados
Foram ainda utilizados os seguintes métodos:
GetSiteID: método que obtém o id do site a partir do nome.
private string GetSiteID(string siteName)
{
string commandToExecute = String.Format(@"{0} /query {1}", IIS_WEB_COMMAND, siteName);
string commandResult = RunShellCommand(commandToExecute);
string siteID = GetRegularExpressionResult(SITEPATH_REGULAR_EXPRESSION, commandResult);
return siteID;
}
RunShellCommand: método que executa um comando externo e obtém o resultado da sua execução.
private string RunShellCommand(string shellCommandToExecute)
{
Process process = new Process();
process.EnableRaisingEvents=false;
//obter nome ficheiro temporário
string tempFileName = String.Format(@"{0}\{1}.txt", Settings.WebSiteCreationTempFolder, DateTime.Now.ToString(FILE_DATETIME_FORMAT));
//executar comando e escrever resultado para ficheiro
string commandArguments = String.Format(@" /c {0} > {1}", shellCommandToExecute, tempFileName);
process.StartInfo.FileName = WINDOWS_COMMAND;
process.StartInfo.Arguments = commandArguments;
process.Start();
process.WaitForExit();
//ler resultados de ficheiro temporário
StreamReader streamReader = new StreamReader(tempFileName);
string commandResults = streamReader.ReadToEnd();
streamReader.Close();
//apagar ficheiro temporário
File.Delete(tempFileName);
//devolver resultado
return commandResults;
}
GetRegularExpressionResult: método que aplica uma expressão regular a uma string.
private string GetRegularExpressionResult(string pattern, string inputString)
{
Regex regex = new Regex(pattern, RegexOptions.IgnoreCase);
MatchCollection matches = regex.Matches(inputString);
return matches.Count == 1 ? matches[0].Value : String.Empty;
}
StartSite: método que inicia o site.
public void StartSite(string siteName)
{
Process new Process();
process.EnableRaisingEvents=false;
string commandArguments = String.Format(@" /start {0} ", siteName);
process.StartInfo.FileName = IIS_WEB_COMMAND;
process.StartInfo.Arguments = commandArguments;
process.Start();
process.WaitForExit();
}
Segurança
Existem questões de segurança associadas à criação de sites no IIS6 a partir de uma aplicação ASP.NET. Por omissão, uma aplicação ASP.NET a correr sobre IIS6, é executada usando uma conta de serviço com permissões muito restritas (conta “Network Service”). Para se criar um site no IIS, é necessário ter permissões de administração. Qualquer solução para resolver este problema implica riscos em termos de segurança, sendo o objectivo encontrar uma solução que mitigue estes riscos. Uma solução possível é a criação de um web service que fica responsável pela criação de sites. Este web service seria colocado no IIS numa segunda Web Application a correr numa Application Pool executada com uma conta de serviço com permissões de administração. Desta forma, a aplicação ASP.NET continuaria a correr com a conta de serviço de permissões restritas, sendo a criação de sites a única operação a correr com permissões de administração. Outras medidas de segurança podem ser implementadas, encontrando-se entre elas efectuar a restrição do acesso ao web service, de forma a que possa ser chamado apenas a partir da máquina onde se encontra a aplicação ASP.NET.
Resumo
A solução aqui apresentada permite efectuar a criação de sites no IIS6 a partir de uma template de site usando uma aplicação ASP.NET. É uma solução bastante flexível, na medida em que pode ser usada para efectuar o aprovisionamento de sites a partir de qualquer template. Para tal, basta criar um ficheiro xml de metabase de um site, resultado da exportação da configuração de um site para ficheiro.