Automatização de Aplicativos Windows usando o AutoHotKey Muitos processos de negócio dependem de aplicativos de terceiros que assumem a presença de um operador humano para executar determinadas rotinas. Isto é particularmente verdadeiro em plataforma MS-Windows, onde, como o próprio nome sugere, estes pacotes consistem em um conjunto de aplicativos com interface gráfica. Neste artigo, procuro mostrar como criar um script robusto para automatizar tarefas rotineiras, com foco em como deixá-lo pronto para uso sob controle do Light/BM. Estrutura de Um Script Ao criar o script de robotização de uma aplicação com o AutoHotkey, é comum (e tentador!) usar o utilitário AutoScriptWriter para capturar a sequência de comandos a ser executada. Embora esta abordagem funcione em ambiente desktop, para o qual o AutoHotkey foi desenvolvido e no qual normalmente queremos apenas eliminar a digitação sequências de teclas, a robotização de uma aplicação em ambiente de produção requer scripts mais robustos. Robustez, neste contexto, significa acima de tudo a capacidade de executar a tarefa em questão e apenas esta tarefa, tratando qualquer desvio não previsto como um erro a ser notificado ao operador. Implementar esta robustez requer uma visão clara do que a aplicação faz, quais os requisitos para que ela opere e quais os resultados esperados. Para se ter uma noção do que isto significa na prática, considere um job qualquer cuja rotina possa ser representada pela seguinte sequência de passos: Iniciar a aplicação; Efetuar o logon na mesma (quando aplicável); Selecionar a função desejada da aplicação, normalmente via menu; Preencher os parâmetros para execução da rotina; Aguardar a finalização da rotina; Fechar a aplicação. Vamos analisar o que deve ser feito em cada passo para tornar a rotina robusta. 1 de 6 Iniciar a aplicação A implementação é trivial: usa-se o comando Run do AutoHotkey. Mas, espere ! Quem disse que a aplicação está onde deveria ? E se estiver e alguém tiver mudado as permissões de acesso ? Robustez, aqui, significa testar se o executável realmente foi criado após o retorno do comando. A forma mais comum de se fazer isto é: Run, aplicativo, , UseErrorLevel, AppPID If ErrorLevel = ERROR { BMLogMessage("Erro iniciando aplicação, cód " . A_LastError ) Exit, -1 } Outro ponto a considerar: Acesso simultâneo à tela. Scripts são particularmente sensíveis às interferências provocadas por usuários ou outras aplicações que estejam rodando simultaneamente. Para reduzir o risco associado a este tipo de situação, a orientação é utilizar o comando BlockInput, bem como fazer uso das rotinas de semáforo do Light/BM: ; Bloqueia usuário durante a execução do script BlockInput, On ; Evita dois scripts do BM ao mesmo tempo neste computador mutex := BMOpenMutex(“BMLOCK_” . %COMPUTERNAME% ) if ( BMWaitMutex(mutex,60000) < 0 ) { BMLogMessage(“Erro esperando mutex”) exit,2 } Algumas observações sobre o trecho acima: O BlockInput bloqueia teclado e mouse; Isto significa que o operador só poderá executar alguma intervenção caso o script finalize ou explicitamente reabilite-os via BlockInput, Off; Note a forma como o nome do semáforo foi criado, levando em conta o nome do computador; Isto significa que apenas um script poderá rodar na mesma máquina; mesmo de outros usuários/sessões; No caso de um servidor Terminal Services, uma forma alternativa é limitar a apenas um; 2 de 6 script por sessão; Isto pode ser feito utilizando-se a variável de ambiente %SESSIONNAME%. Efetuar o Logon A tela de logon costuma ser um diálogo modal que surge logo após a aplicação ser iniciada. Outro cenário possível é termos uma opção de menu que, por sua vez, abre o diálogo modal. Fora usuário e senha, é possível que se tenha campos adicionais a preencher, indicando o servidor de banco de dados, p.ex.A sequência normal usando WinWaitActive é a recomendada, porem use sempre a variante com timeout e verifique após a saída de a janela apareceu: WinWaitActive, Logon,,5 IfWinNotActive, Logon { BMLogMessage("Timeout aguardando tela de logon") Exit,-1 } Uma vez que a tela tenha aparecido, preencha os campos usando o Send. Para navegação entre campos, dê preferência ao uso dos aceleradores no lugar de teclas de navegação ({tab}) ou simulação de mouse. Outro ponto importante é quanto ao tratamento da senha em si. Em um ambiente de produção, a senha deve estar "hardcoded" no script (diretamente ou via #include), o qual será compilado em executável pela área de segurança. Esta técnica permite atender o requisito de que o operador não deve ter acesso a uma senha de aplicação. Após o envio de usuário/senha, deve-se verificar possíveis erros. Os mais comuns são: Usuário/senha inválido; Senha expirada; Erro de acesso ao servidor. A tática mais comum para tratar estes erros consiste em iniciar um loop onde se verifica as possíveis situações de erro. Este loop controla também a duração total de execução do script, o qual, via de regra, deve ter sempre um mecanismo de timeout. Uma forma simples e efetiva de implementar este controle é por meio da variável A_TickCount, que retorna um valor inteiro que corresponde ao número de milissegundos decorridos desde o último boot. 3 de 6 ; Salva o instante em que começamos loopStart := A_TickCount elapsed := 0 ; Loop de 1 minuto (60s * 1000) While elapsed < 60000 { ; Testa erro de logon IfWinActive, Erro ahk_class #32770 { WinGetText, msg BMScreenShot() BMLogMessage("Erro efetuando logon :" . msg ) Send, !{F4} Exit, -10 } ; .... Outros testes ; Apareceu a janela principal ? IfWinActive, Aplicativo { BMLogMessage("Logon efetuado com sucesso") Break } ; Se não aconteceu nada, dorme um pouco para não ; monopolizar a CPU Sleep, 100 ; Atualiza tempo decorrido elapsed := A_TickCount - loopStart } Algumas observações: ahk_class #32770 indica um "message box" padrão do windows. O valor é obtido pelo utilitário AutoIt3 Windows Spy, que acompanha a distribuição do AutoHotkey; Note o uso generoso de mensagens de log em todos os pontos de falha. Em caso de erro, a produção é responsável por passar o máximo de detalhes do ocorrido, e nada melhor que estas mensagens para descobrir o que aconteceu. Preencher Parâmetros Este passo, embora possa ser trabalhoso no caso de telas complexas, normalmente requer menos tratamentos de erro, já que a sequência de preenchimento, em princípio, deve estar 4 de 6 correta. Procure, no entanto, verificar o que acontece quando dados incorretos forem inseridos nos campos. É comum surgirem "popups" que devem ser removidos para que o processo continue. Como no caso do login, dê preferência à navegação entre campos por meio dos aceleradores. Isto torna o script menos sensível a eventuais mudanças no layout do formulário que alterem a ordem de navegação. Infelizmente, são poucas a as aplicações "in-house" que utilizam de forma correta este tipo de recurso...Um caso particular de preenchimento é a seleção de um ou mais itens de uma lista de valores apresentada em um "listbox". É comum que esta lista seja montada dinamicamente a partir de uma consulta à base. Isto significa que seu conteúdo pode variar entre execuções. Digamos que a automação requer a seleção de um item específico (p.ex, o nome de um cliente, passado via parâmetro para o script). Como resolver a situação ?Simples(ou quase). Utilize a função ControlGet. Esta função possui vários sub-comandos, dentre os quais o que resolve este caso é o List. Com ele, você recupera em uma variável toda a lista contida no controle, e pode processá-la em um Loop, Parse: ; Assumo que o foco já está no ListBox ControlGet, Opcoes,List,,ListBox1 Loop, Parse, Opcoes, `n { opcao=%A_LoopField% if ( opcao = "Joao" ) { ; Seleciona o item Send, {SPACE}Break } ; Desce para o próximo Send, {DOWN} } Aguardar a finalização da rotina Mais uma vez, a melhor técnica é implementar um loop de espera, onde a cada ciclo verifica-se a presença de erros e/ou de um indicador de final da operação. Tanto um quanto outro costumam ser identificados por uma combinação das funções IfWinExist, IfWinActive ou semelhantes, em uma estrutura idêntica à fase de logon. Um ponto que pode passar despercebido é que, em alguns cenários, a aplicação sob controle do script pode ser finalizada por outros meios, mesmo com a entrada inibida. Por exemplo, o administrador da máquina pode utilizar um utilitário para "matar" o processo, o que provoca o "desaparecimento" das janelas. Para evitar que o script fique aguardando até o fim do prazo 5 de 6 limite para identificar esta situação, utilize a função Process, Exist, passando como argumento o identificador do processo que foi obtido ao iniciar a aplicação: ; Espera até 3min (aprox.) para finalização. loopStart := A_TickCount elapsed := 0 While elapsed < 180000 { ; Testes para identificar erros IfWinExist, Erro ahk_class #32770 { BMScreenShot() Break } ; Teste para fim da operação IfWinExist, Informação ahk_class #32770 { BMScreenShot() Break } ; Aplicação está viva ? Process, Exist, %AppPID% If ErrorLevel = 0 { BMLogMessage("Aplicação terminou de forma inesperada !") Break } ; Espera de 100ms Sleep, 100 ; Atualiza tempo decorrido elapsed := A_TickCount - loopStart } Conclusão Scripts de automação bem construídos são fundamentais para operar de forma suave sua produção, especialmente em um ambiente onde estas rotinas operam sob a supervisão do Lighthouse/BM. As técnicas apresentadas neste artigo, desenvolvidas a partir de casos reais, servem como referência para atingir este objetivo. Referências Adicionais: AutoHotKey - Site do pacote descrito neste artigo 6 de 6