Minimizamos as possibidades de erro deste processo criando um instalador em Delphi para realizar a parte mais braçal. Ele se baseia nos recursos da tecnologia WMI (Windows Management Instrumentation) para o IIS, o que permite criar sites e pools de aplicação, gerenciar as propriedades de ambos, adicionar tipos de arquivos reconhecidos pela aplicação (MIMEs), aplicar permissões de acesso, iniciar, interromper e reciclar sites, etc.
As classes WMI para acessar esses recursos não são instalados por padrão com o IIS mas podemos garantir que eles estejam presentes com o DISM, programa do Windows que serve, entre outras coisas, para gerenciar quais recursos do sistema operacional estarão habilitados. A linha de comando a seguir habilita as ferramentas para gerenciar o ISS via WMI; ela deve ser executada no servidor do IIS com o usuário Administrador para ter efeito.
dism.exe /Online /Enable-Feature /FeatureName:IIS-WebServerManagementTools /FeatureName:IIS-ManagementScriptingTools
O comando acima pode ser extendido para incluir a instalação do IIS e ativar os recursos que sejam necessários para o funcionamento do seu site, tais como extensões e filtros ISAPI, aplicações ASP ou CGI, etc.
Falei em outra ocasião aqui no blog sobre o WMI, tecnologia da Microsoft para administração centralizada de diversos aspectos de um computador. No post Obtendo nível de sinal do Wifi usando WMI com Delphi eu importei a interface COM do WMI, obtendo acesso às funções desse mecanismo. Neste post, usarei um recurso do Delphi para tornar o acesso mais prático: late binding; ou seja, acessarei diretamente os nomes das funções e propriedades de uma classe WMI, deixando para a linguagem resolvê-los em tempo de execução.
Antes de partir para qualquer tipo de ação, precisamos obter em nosso programa uma instância do WMI. O WMI é organizado hierarquicamente, tendo uma raiz (o namespace) para cada grupo de aspectos gerenciáveis do computador, representados por classes. Dado um nome de computador e um namespace, podemos obter a respectiva instância do WMI através da função abaixo:
function GetWMIObject(wmiHost, wmiRoot: string): IDispatch;
var chEaten: Integer;
BindCtx: IBindCtx;
Moniker: IMoniker;
objectName : String;
begin
{ Monta o nome do objeto WMI desejado }
objectName := Format('winmgmts:\\%s\%s',[wmiHost,wmiRoot]);
OleCheck(CreateBindCtx(0, bindCtx));
{ Obtém um "moniker" para o objeto indicado pelo nome montado a partir do host e do root }
OleCheck(MkParseDisplayName(BindCtx, StringToOleStr(objectName), chEaten, Moniker));
{ Recupera o IDispatch para facilitar o uso do objeto no programa }
OleCheck(Moniker.BindToObject(BindCtx, nil, IDispatch, Result));
end;
var chEaten: Integer;
BindCtx: IBindCtx;
Moniker: IMoniker;
objectName : String;
begin
{ Monta o nome do objeto WMI desejado }
objectName := Format('winmgmts:\\%s\%s',[wmiHost,wmiRoot]);
OleCheck(CreateBindCtx(0, bindCtx));
{ Obtém um "moniker" para o objeto indicado pelo nome montado a partir do host e do root }
OleCheck(MkParseDisplayName(BindCtx, StringToOleStr(objectName), chEaten, Moniker));
{ Recupera o IDispatch para facilitar o uso do objeto no programa }
OleCheck(Moniker.BindToObject(BindCtx, nil, IDispatch, Result));
end;
O resultado deste código é a instância do WMI criada como uma interface genérica IDispatch. Para ter acesso aos recursos do IIS no computador local, chamamos esta função passando o valor '.' como Host e 'root\WebAdministration' como raiz.
var FWmiObj : IDispatch;
begin
FWmiObj := GetWMIObject('.', 'root\WebAdministration');
{ ... }
begin
FWmiObj := GetWMIObject('.', 'root\WebAdministration');
{ ... }
De posse da instância do WMI, agora podemos iniciar a administração de sites propriamente dita. No caso do ERP Pronto da ABC71, nós criamos um Application Pool específico para isolar a execução de nossa aplicação, protegendo-a de problemas que eventualmente surjam em outros sites no mesmo servidor. Usando o WMI, podemos perguntar ao IIS se o pool já está criado e, então, criá-lo se for necessário:
var FObj, FEnum, FItem, FAppPool: OLEVariant;
lEnum: IEnumVariant;
FNome: String;
qtde: LongWord;
begin
FObj := FWmiObj;
FNome := 'ERPProntoServApp';
FENum := FObj.ExecQuery('SELECT * FROM ApplicationPool WHERE Name="' + FNome + '"', 'WQL', 0)._NewEnum;
{ Recupera instância do IEnumerateVariant para poder navegar pelos registros encontrados. }
lEnum := IUnknown(FEnum) As IEnumVariant;
{ Navega para o 1o registro do conjunto. Se achou, FItem contem o item encontrado. }
if (lEnum.Next(1, FItem, qtde) <> 0) then begin
{ Não achou, cria o pool aqui }
FAppPool := FObj.Get('ApplicationPool');
FAppPool.Create(FNome, false);
{ Localiza o pool recém criado para poder configurá-lo }
FENum := FObj.ExecQuery('SELECT * FROM ApplicationPool WHERE Name="' + FNome + '"', 'WQL', 0)._NewEnum;
lEnum := IUnknown(FEnum) As IEnumVariant;
if (lEnum.Next(1, FItem, qtde) <> 0) then
Raise Exception.Create ('Não foi possível criar pool ' + FNome);
end;
{ Outras configurações para o Pool }
FItem.AutoStart := true;
FItem.Enable32BitAppOnWin64 := true;
FItem.ManagedPipelineMode := 0; {integrado }
FItem.ManagedRuntimeVersion := ''; { sem .NET / código gerenciado }
FItem.RapidFailProtection := false;
{ Efetiva as alterações, gravando-as no IIS }
FItem.Put_();
{ ... }
lEnum: IEnumVariant;
FNome: String;
qtde: LongWord;
begin
FObj := FWmiObj;
FNome := 'ERPProntoServApp';
FENum := FObj.ExecQuery('SELECT * FROM ApplicationPool WHERE Name="' + FNome + '"', 'WQL', 0)._NewEnum;
{ Recupera instância do IEnumerateVariant para poder navegar pelos registros encontrados. }
lEnum := IUnknown(FEnum) As IEnumVariant;
{ Navega para o 1o registro do conjunto. Se achou, FItem contem o item encontrado. }
if (lEnum.Next(1, FItem, qtde) <> 0) then begin
{ Não achou, cria o pool aqui }
FAppPool := FObj.Get('ApplicationPool');
FAppPool.Create(FNome, false);
{ Localiza o pool recém criado para poder configurá-lo }
FENum := FObj.ExecQuery('SELECT * FROM ApplicationPool WHERE Name="' + FNome + '"', 'WQL', 0)._NewEnum;
lEnum := IUnknown(FEnum) As IEnumVariant;
if (lEnum.Next(1, FItem, qtde) <> 0) then
Raise Exception.Create ('Não foi possível criar pool ' + FNome);
end;
{ Outras configurações para o Pool }
FItem.AutoStart := true;
FItem.Enable32BitAppOnWin64 := true;
FItem.ManagedPipelineMode := 0; {integrado }
FItem.ManagedRuntimeVersion := ''; { sem .NET / código gerenciado }
FItem.RapidFailProtection := false;
{ Efetiva as alterações, gravando-as no IIS }
FItem.Put_();
{ ... }
Para forçar o Delphi a usar late binding, declaro uma variável OLEVariant e lanço nela o objeto WMI instanciado. Com isso, posso inquerir a classe ApplicationPool sem me preocupar em declarar a função ExecQuery do WMI. O resultado da chamada a essa função é um variant contendo um IEnumVariant, interface que permite percorrer a lista de registros encontrados. Como este resultado não implementa a interface IDispatch, o Delphi não consegue descobrir seus métodos e propriedades automaticamente, razão pela qual a conversão explícita é obrigatória neste caso. Já para os itens extraídos do enumerado, podemos contar com o late binding normalmente, conforme demonstra o código.
Se o ApplicationPool com o nome proposto não for encontrado pela consulta, o programa cria o pool. Isto é feito obtendo uma instância estática da classe de pools e invocando o método Create dela. Após isto, o pool já está criado e pode ser posicionado normalmente através de query.
Nossa aplicação é construída em C++ 32 bits, isto é, sem código gerenciado da plataforma .NET. Assim, o final do código é reservado para garantir que o pool execute corretamente neste ambiente, ligando as configurações apropriadas e gravando-as no IIS.
Num próximo post, mostro como criar a aplicação no IIS e associá-la ao pool criado aqui.