Team Foundation Build 2010: Como Implantar Windows Services usando o PowerShell (pt-BR)

Team Foundation Build 2010: Como Implantar Windows Services usando o PowerShell (pt-BR)

 Este artigo foi publicado originalmente na revista Mundo.NET número 27 de junho/julho de 2011 e foi reproduzido aqui a fim de ampliar o seu alcance e permitir que a comunidade possa corrigir qualquer imprecisão ocorrida ou contribuir para melhorar seu conteúdo.

Implantando Windows Services com o Team Foundation Build 2010

Ampliando as possibilidades com o Windows PowerShell


Introdução

A utilização do Windows PowerShell oferece uma alternativa bastante flexível para estender as funcionalidades do Team Foundation Build 2010. A partir da customização de um build process template é possível realizar chamadas a scripts que podem executar diferentes tipos de tarefas. A abordagem deste artigo é a utilização desses recursos para realizar a implantação de um projeto do tipo Windows Service em um servidor de testes a partir da execução de uma build.

Cenário

O processo de build de uma aplicação em desenvolvimento pode incluir sua implantação em um determinado ambiente, como o ambiente de testes. Através do Team Foundation Build você pode realizar a implantação de projetos do tipo ASP.NET Web Application, por exemplo, utilizando o Web Deploy. Com ele é possível passar argumentos ao MSBuild na definição da build e a partir daí, como parte do seu processo de execução, fazer com que o MSDeploy seja chamado a fim de que seja realizada a implantação da aplicação no servidor IIS desejado.

Mais informações sobre Web Deploy, MSDeploy e sua utilização:
http://www.iis.net/download/WebDeploy
http://blogs.allmatech.com.br/Blogs/ALMTeam/post/Creating-a-Build-Definition-to-work-with-MSDeploy.aspx

Para projetos do tipo Windows Service é necessário um pouco mais de trabalho. Para poder realizar a instalação de um serviço, caso o serviço já esteja instalado, é preciso realizar primeiro a desinstalação da versão atual antes de substituir os arquivos desta pelos arquivos da nova build e realizar sua instalação através do utilitário installutil do .NET Framework.

A solução apresentada tem como objetivo realizar a implantação, em um servidor de testes, de um projeto do tipo Windows Service a partir da execução de uma definição de build do Team Foundation Build 2010. Para realizar essa tarefa, foi customizado um build process template de forma a incluir em seu fluxo de trabalho uma chamada a um script do Windows PowerShell. Este script por sua vez, tem a capacidade de executar comandos remotamente no servidor de testes para desinstalar a versão do serviço instalada atualmente e em seguida instalar a nova versão gerada pela execução da build. Outro recurso importante implementado na solução é a capacidade de realizar “config transformation”. Este recurso foi incluído na versão 2010 do Visual Studio somente para web applications (conhecido como Web.config Transformation) e permite realizar transformações no arquivo de configuração da aplicação, o  Web.config, substituindo informações presentes nele de acordo com a configuração de compilação do projeto (Debug, Release ou outra criada pelo usuário), gerando assim um arquivo com configurações próprias para cada uma delas. Apesar deste recurso não estar disponível explicitamente na interface do Visual Studio 2010 para outros tipos de projetos, você verá que é possível utilizá-lo modificando o arquivo de definição do projeto.

A Figura 1 ilustra os componentes envolvidos na solução apresentada. A definição de build MyBuildDefinition, configurada para utilizar o modelo WindowsServiceTemplate.xaml e copiar o resultado para a pasta \\SRV_BUILD\Drop, está configurada também para compilar o projeto MyWindowsServices.csproj em modo RELEASE a fim de que as transformações definidas no arquivo App.Release.config sejam aplicadas sobre o arquivo App.config no sentido de gerar o arquivo de configuração correspondente ao ambiente de testes.


Figura 1 - Componentes da solução

Ao executar a build, de acordo com a customização realizada no modelo, o script WindowsServiceTemplate.ps1 é chamado e, ao ser executado, pára os serviços definidos no projeto que estão atualmente em execução no servidor de testes, SRV_TESTE, e em seguida executa o comando installutil –u neste servidor para proceder a desinstalação. Depois disso, copia o conteúdo da nova build a partir da pasta compartilhada \\SRV_BUILD\Drop para a pasta C:\MyWindowsService do servidor SRV_TESTE, sobreescrevendo os arquivos atuais, e finalmente executa o comando installutil novamente para instalar a nova versão.

Preparando o ambiente para execução do script

Alguns comandos do Windows PowerShell precisam ser executados nos servidores envolvidos no processo para permitir a execução do script desta solução.

Primeiramente pode ser necessário configurar o Windows Remote Management e habilitar a execução de comandos remotos para o Windows PowerShell entre os servidores. Os comandos da Listagem 1 devem ser executados nos servidores SRV_BUILD e SRV_TESTE.

Listagem 1. Habilitando a execução de comandos remotos

winrm quickconfig
Enable-PSRemoting -Force

Para saber mais sobre o Windows Remote Management  (WinRM):
http://msdn.microsoft.com/en-us/library/aa384291(VS.85).aspx

O comando listado a seguir é executado no servidor SRV_BUILD e permite que o script da solução seja executado neste servidor sem restrições.

Listagem 2. Habilitando a execução do script no servidor de build

Set-ExecutionPolicy ByPass -Force

ATENÇÃO! Esse comando permite que qualquer script não assinado ou baixado da Internet seja executado no servidor. O método apresentado foi escolhido por ser mais simples para demonstração. A opção mais segura nesse caso seria assinar o script digitalmente de forma que ele pudesse rodar com a opção “AllSigned” do comando Set-ExecutionPolicy. Para mais informações, consulte http://technet.microsoft.com/pt-br/library/dd347649.aspx

Outra configuração importante diz respeito a permissões. A chamada ao script será realizada pela execução da definição da build, logo, o script será executado no servidor de build. Porém, o comando para cópia dos arquivos da build (neste caso o comando utilizado é o ROBOCOPY) será executado no servidor de testes, SRV_TESTE. Assim nosso cenário é o seguinte:

  1. O script executado no servidor SRV_BUILD executa o comando ROBOCOPY remotamente no servidor SRV_TESTE a fim de que sejam copiados os arquivos da build de SRV_BUILD para SRV_TESTE.
  2. O script precisa passar uma credencial ao servidor SRV_TESTE que possua permissão para executar o comando ROBOCOPY neste servidor.
  3. Quando o comando ROBOCOPY é executado, a credencial informada precisa ter permissão de leitura na pasta que contém os arquivos da build (localizada em \\SRV_BUILD\Drop) e de gravação na pasta local, C:\MyWindowsService. È importante destacar ainda que a mesma credencial será utilizada para instalar o serviço e portanto precisará de permissão para isso também no servidor SRV_TESTE.

Neste cenário, a credencial informada precisa ser passada pelo script do servidor SRV_BUILD para o servidor SRV_TESTE e logo em seguida, pelo execução do comando ROBOCOPY, do servidor SRV_TESTE para o servidor SRV_BUILD, conforme mostrado na Figura 2.


Figura 2 - Copiando os arquivos da build

O problema com este cenário é que esse “salto duplo” da credencial não é permitido. Esta questão é conhecida como double-hop e é uma característica do modelo de autenticação utilizado em redes baseadas no Active Directory.

Para saber mais sobre a questão do “double-hop”:
http://support.microsoft.com/kb/329986/pt-br

Entretanto, o Windows PowerShell oferece uma solução para este problema. Através da execução de comandos apropriados nos servidores envolvidos, é possível permitir a delegação de credenciais de um servidor para outro, conforme listado a seguir.

Listagem 3. Habilitando delegação de credenciais

#No servidor de testes (SRV_TESTE)
Enable-WSManCredSSP -Role Server -Force

#No servidor de build (SRV_BUILD)
Enable-WSManCredSSP -Role Client -DelegateComputer SRV_TESTE -Force

O primeiro comando, executado no servidor SRV_TESTE, habilita este servidor a delegar credenciais para outros servidores. O segundo comando, executado em SRV_BUILD, habilita este servidor a aceitar credenciais delegadas pelo servidor SRV_TESTE.

Criando o script

Com o ambiente configurado, o passo seguinte é a criação do script Windows PowerShell responsável pela implantação do projeto. Para criação do script do arquivo WindowsServiceTemplate.ps1 foi utilizado o Windows PowerShell ISE, disponível nativamente no Windows 7 e Windows Server 2008 R2. Este editor, além de exibir o código com cores, semelhante ao Visual Studio, permite realizar a depuração passo a passo do código, permitindo inclusive adicionar breakpoints.

Para saber mais sobre o Windows PowerShell ISE:
http://technet.microsoft.com/pt-br/library/dd819514.aspx

Como vimos anteriormente, o script precisará de uma credencial com permissões específicas para cópia dos arquivos da build e instalação do serviço no servidor de testes. Além do nome do usuário e a senha correspondentes a essa credencial, outros parâmetros precisarão ser passados ao script para sua execução, sendo eles:

  • Nome do servidor de testes.
  • Nomes dos serviços incluídos no projeto.
  • Caminho para a build drop folder, que é a pasta compartilhada da rede para onde o resultado da build é copiado.

Listagem 4. Passando parâmetros

param($serverName, [string[]]$serviceNames, $buildPath, $serviceExecutableLocalPath, $user, $pwd)

Todos os parâmetros são sequências simples de caracteres, à execção do parâmetro $serviceNames que é um string array; isso porque um projeto do tipo Windows Service pode conter vários serviços e será necessário passar o nome de cada um deles. O comando installutil irá instalar todos os serviços, mas posteriormente eles deverão ser iniciados um a um. Importante destacar aqui que o nome a ser informado é o correpondente à propriedade “ServiceName” e não “DisplayName”. Ambas as propriedades são definidas no instalador de cada serviço incluído no projeto.

Após definir os parâmetros (esta definição precisa ser obrigatoriamente a primeira linha do script), são definidas algumas variáveis globais que serão utilizadas pelo script.

Listagem 5. Variáveis: credencial e caminhos utilizados

$pwd = ConvertTo-SecureString $pwd -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user, $pwd
$installUtil = Join-Path $env:SystemRoot Microsoft.NET\Framework\v2.0.50727\installutil.exe
$localFolderPath = $serviceExecutableLocalPath.Substring(0, $serviceExecutableLocalPath.LastIndexOf('\'))

A variável $credential, criada a partir do usuário e senha informados como parâmetros, representa a credencial que será utilizada para copiar os arquivos da build e instalar o serviço. A variável $installUtil representa o caminho para o executável do utilitário installutil e $localFolderPath corresponde à pasta local do servidor de testes para onde os arquivos da build serão copiados.

Iniciando o processo de implantação, o script verifica se o executável do projeto já está instalado. Caso ele esteja, define que ele precisará ser desinstalado. Entretanto, antes de proceder a desinstalção, é possível que algum processo correspondente aos serviços do projeto continue em execução por um tempo ainda após a desinstalação; isso pode ocasionar problemas posteriormente. Por isso, conforme se vê na Listagem 6 , cada um dos serviços é parado e em seguida um loop é executado a cada cinco segundos até que o processo esteja definitivamente encerrado.

Listagem 6. Encerrando os processos em execução

$uninstallService = $false
foreach($svc in $serviceNames)
{
     $service = Invoke-Command -ComputerName $servername -Credential $credential -ScriptBlock {param($svc) ; Get-WmiObject win32_service | where {$_.name -eq $svc}} -ArgumentList $svc
     if ($service -ne $null)
     {
         Invoke-Command -ComputerName $servername -Credential $credential -ScriptBlock {param($svc) ; Stop-Service $svc} -ArgumentList $svc
         do
         {
             Start-Sleep -s 5
             $proc = Invoke-Command -ComputerName $servername -Credential $credential -ScriptBlock {param($svc) ; Get-Process "$svc" -ea 'Stop'} -ArgumentList $serviceName -ErrorAction SilentlyContinue
         } while ($proc -ne $null)
         $uninstallService = $true
     }
}

Somente após o encerramento de todos os processos a desinstalação é realizada. Perceba que a variável $uninstallService foi criada para indicar se é necessário desinstalar o serviço. Seu valor é definido para $true apenas se algum serviço for encontrado.

Na sequência, caso algum serviço tenha sido encontrado, é executado o comando para desinstalação do executável. Caso contrário, no caso da primeira instalação, é criada a pasta no servidor de testes para receber os arquivos da nova build. Repare que caso a pasta já exista, o processo é encerrado através do retorno de uma exceção pelo comando Throw.

Listagem 7. Desinstalando

if ($uninstallService)
{
     Invoke-Command -ComputerName $servername -Credential $credential -ScriptBlock {param($cmd, $path) ; & "$cmd" -u "$path"} -ArgumentList $installUtil, $serviceExecutableLocalPath
}
else
{
    $testPath = Invoke-Command -ComputerName $serverName -Credential $credential -ScriptBlock {param($localPath) ; Test-Path "$localPath"} -ArgumentList $localFolderPath
    if (-Not $testPath)
    {
        Invoke-Command -ComputerName $serverName -Credential $credential -ScriptBlock {param($localPath); New-Item -Type directory -Path "$localPath"} -ArgumentList $localFolderPath
    }
    else
    {
        Throw $("A pasta '$localFolderPath' já existe no servidor '$serverName'. Operação encerrada.")
    }
}

O passo a seguir é a cópia dos arquivos da nova build do servidor de build para a pasta correspondente no servidor de testes através do comando ROBOCOPY.

Listagem 8. Copiando arquivos da build

$cmdCopy = Invoke-Command -ComputerName $servername -Credential $credential -Authentication CredSSP -ScriptBlock {param($source, $path) ; robocopy /E /PURGE $source $path} -ArgumentList $buildpath, $localFolderPath

Com os arquivos copiados, o comando installutil é executado para realizar a instalação dos serviços, conforme se vê na Listagem 9.

Listagem 9. Instalando os serviços

Invoke-Command -ComputerName $servername -Credential $credential -ScriptBlock {param($cmd, $path) ; & "$cmd" "$path"} -ArgumentList $installUtil, $serviceExecutableLocalPath

Com os serviços devidamente instalados, eles são iniciados através do código da Listagem 10.

Listagem 10. Iniciando os serviços

foreach($svc in $serviceNames)
{
     Invoke-Command -ComputerName $servername -Credential $credential -ScriptBlock {param($svc) ; Start-Service $svc} -ArgumentList $svc
}

Com o script pronto, o próximo passo é criar um build process template que seja capaz de executá-lo.

Criando um novo build process template

O modelo utilizado nesta solução é uma cópia do modelo DefaultTemplate.xaml que é gerado automaticamente durante a criação de um novo team project no Team Foundation Server. A cópia foi nomeada como WindowsServiceTemplate.xaml e foi modificada para incluir uma chamada ao script conforme você verá a seguir.

Para saber mais sobre customização de um build process template:
http://msdn.microsoft.com/en-us/library/dd647551.aspx

Primeiro são definidos os argumentos que a definição da build deverá receber para ser executada, conforme se vê na Figura 3.


Figura 3 - Argumentos

Repare que todos os argumentos são do tipo String, à exceção do argumento ServiceNames que é um string array. Os valores desses argumentos serão passados como parâmetros ao script e por isso é necessário manter a consistência com os tipos de dados definidos nele, conforme visto na Listagem 4. Passando parâmetros.

Para fins de organização, a Figura 4 mostra que para cada argumento é possível definir como ele será exibido na janela de definição da build, definindo um nome amigável, uma categoria e um texto explicativo. Isso é feito através da edição do argumento Metadata.


Figura 4 - Organizando os parâmetros

Em seguida, o modelo é customizado no sentido de incluir as atividades necessárias para a execução do script. Conforme mostrado na Figura 5, logo abaixo da atividade já existente “Try Compile, Test, and Associate Changesets and Work Items”, são incluídas essas novas atividades. A Figura 6 mostra as propriedades definidas para cada nova atividade incluída.


Figura 5 - Atividades


Figura 6 - Propriedades das atividades

Repare que a chamada ao script é feita através da atividade InvokeProcess, que permite a execução de uma linha de comando. O que ela faz é executar o comando PowerShell passando como parâmetros o caminho para o arquivo de script criado e os argumentos definidos para ele.

Implementando “config transformation

O último passo antes de executar a build é preparar o projeto MyWindowsService.csproj para que ele seja implementado no servidor de testes com as configurações apropriadas ao ambiente.

Suponha que o arquivo App.config do projeto possua uma string de conexão a um banco de dados local (localhost), conforme mostrado na Listagem 11, que é utilizado para desenvolvimento.

Listagem 11. Arquivo App.config de desenvolvimento

<?xml version="1.0" encoding="utf-8"?>
<configuration>
     <connectionStrings>
         <add name="MyConnectionString" connectionString="Data Source=localhost;Initial Catalog=MyDatabase;Integrated Security=True" providerName="System.Data.SqlClient" />
     </connectionStrings>
</configuration>

No ambiente de testes, a string de conexão deverá apontar para o banco de dados de testes. Para criar um arquivo de transformação, da mesma forma como é feito em projetos web utilizando o recurso Web.config Transformations do Visual Studio 2010, é preciso realizar algumas modificações no arquivo do projeto.

Para saber mais sobre Web.config Transformation:
http://msdn.microsoft.com/en-us/library/dd465326.aspx

Com o projeto aberto no Visual Studio, as modificações necessárias são as seguintes:

  1. Adicione um novo arquivo de configuração ao projeto com o nome App.Release.config. Este arquivo será utilizado para realizar transformações quando o projeto for compilado em modo RELEASE.
  2. Adicione as tranformações desejadas no novo arquivo. Neste caso, conforme mostrado na Listagem 12, a string de conexão passa a apontar para o servidor de banco de dados de testes (MyDBTestServer).

    Listagem 12. Transformação do arquivo App.Release.config

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
         <connectionStrings>
             <add name="MyConnectionString" connectionString="Data Source=MyDBTestServer;Initial Catalog=MyDatabase;Integrated Security=True" providerName="System.Data.SqlClient" xdt:Locator="Match(name)" xdt:Transform="SetAttributes" />
         </connectionStrings>
    </configuration>
  3. Clicar com o botão direito do mouse sobre o nome do projeto na janela do Solution Explorer e escolher o comando Unload Project.
  4. Com o projeto indicado como unavailable, clicar novamente com o botão direito sobre o nome do projeto e escolher Edit MyWindowsServices.csproj. Isso irá abrir o arquivo do projeto, que é um arquivo XML, para edição.
  5. Encontrar as referências para os arquivos App.config e App.Release.config. Elas deverão estar indicadas conforme a Listagem 13.

    Listagem 13. Referências iniciais aos arquivos de configuração

    <ItemGroup>
           <None Include="App.config" />
           <None Include="App.Release.config" />
    </ItemGroup>
  6. Alterar as referências dos arquivos para incluir a dependência necessária entre eles conforme mostra a Listagem 14.

    Listagem 14. Implementação da dependência entre os arquivos de configuração

    <ItemGroup>
         <Content Include="App.config" />
         <Content Include="App.Release.config">
             <DependentUpon>App.config</DependentUpon>
         </Content>
    </ItemGroup>
  7. No final do arquivo, imediatamente antes da tag </Project>, incluir o XML mostrado na Listagem 15.

    Listagem 15. XML responsável pela transformação

    <UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
    <Target Name="AfterCompile" Condition="exists('app.$(Configuration).config')">
         <TransformXml Source="app.config" Destination="$(IntermediateOutputPath)$(TargetFileName).config" Transform="app.$(Configuration).config" />
         <ItemGroup>
             <AppConfigWithTargetPath Remove="app.config" />
             <AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config">
                 <TargetPath>$(TargetFileName).config</TargetPath>
             </AppConfigWithTargetPath>
         </ItemGroup>
    </Target>

Agora o projeto está pronto para ser compilado e implantado no servidor de testes.

Implantando o projeto

Com toda a estrutura preparada, finalmente é possível criar uma nova definição de build baseada no modelo customizado. As propriedades a serem definidas conforme a Figura 1 - Componentes da solução são as seguintes:


Figura 7 – Definição de build

Ao executar a definição de build MyBuildDefinition, o projeto MyWindowsServices.csproj será compilado em modo RELEASE. Isso resultará na aplicação das transformações definidas no arquivo App.Release.config sobre o arquivo App.config. Os arquivos resultantes da compilação do projeto, incluindo o arquivo de configuração transformado, serão copiados para a pasta C:\MyWindowsService do servidor SRV_TESTE e o arquivo MyWindowsService.exe será instalado nesse servidor, via utilitário installutil. Finalmente, encerrando o procedimento, os serviços MyWindowsService1 e MyWindowsService2 definidos no projeto são iniciados.

Roadmap

Todos os servidores desta solução utilizaram o Windows Server 2008 R2 como sistema operacional por já possuir suporte nativo ao Windows PowerShell 2.0 e ao WinRM 2.0. O Team Foundation Server 2010 e o Team Foundation Build 2010, instalados nesses servidores, estão perfeitamente integrados à plataforma de 64 bits, que aliada ao .NET Framework 4 oferece um ambiente estável e seguro para execução do processo de build de aplicações. O projeto MyWindowsServices.csproj utilizado como exemplo foi criado com o Visual Studio 2010.

Assim como aconteceu com outros produtos da Microsoft, como SharePoint e Exchange, a integração entre o Windows PowerShell e o Team Foundation Server é uma tendência natural para a próxima versão do TFS, o que demonstra a consolidação do Windows PowerShell como recurso fundamental para a administração de servidores.

Considerações Finais

O Windows PowerShell foi criado primeiramente para ser um shell de linha de comando e linguagem de script para a administração de sistemas. Este artigo mostra que é possível utilizá-lo para muito mais do que isso. Por ser baseado no .NET Framework, permite utilizar o seu modelo de objetos e estender as funcionalidades de outros produtos baseados nessa arquitetura de uma maneira bastante rápida e flexível. Por isso ele se torna cada vez mais uma ferramenta indispensável tanto pra profissionais de infraestrutura quanto para desenvolvedores.

Referências

Código Fonte

Clique aqui para baixar o código fonte deste artigo.

Leave a Comment
  • Please add 5 and 7 and type the answer here:
  • Post
Wiki - Revision Comment List(Revision Comment)
Sort by: Published Date | Most Recent | Most Useful
Comments
  • Eduardo Assis edited Revision 3. Comment: Alteração da URL de download do código fonte  

  • Luciano Lima [MVP] Brazil edited Revision 1. Comment: Corrigido erro de digitação na nota no inicio do artigo.

  • Eduardo Assis edited Original. Comment: Correção do link para download do código fonte que não estava aparecendo e do título errado para "Implementando config transformation"

Page 1 of 1 (3 items)
Wikis - Comment List
Sort by: Published Date | Most Recent | Most Useful
Posting comments is temporarily disabled until 10:00am PST on Saturday, December 14th. Thank you for your patience.
Comments
  • Eduardo Assis edited Original. Comment: Correção do link para download do código fonte que não estava aparecendo e do título errado para "Implementando config transformation"

  • Luciano Lima [MVP] Brazil edited Revision 1. Comment: Corrigido erro de digitação na nota no inicio do artigo.

  • Eduardo Assis edited Revision 3. Comment: Alteração da URL de download do código fonte  

Page 1 of 1 (3 items)