30 de dezembro de 2011

Preparando a execução de um programa em desktop remoto

A adoção maciça da internet e os benefícios associados a ela têm levado as empresas a optar cada vez com mais frequência por programas que possam ser executados nessa infraestrutura. O principal benefício aqui está relacionado aos custos já que os desktops não precisam ser tão potentes - basta que tenham acesso à internet para executarem esses programas remotos - e a manutenção dos equipamentos passa ser centralizada.

Por outro lado, nem todos os programas em uso pelas empresas operam nativamente na internet. Seja por que são programas legados ou por que o fabricamente ainda não os preparou para o ambiente web ou simplesmente porque há muitos programas e não houve tempo hábil para uma conversão em massa. Por isso, programas como o RDS (Remote Desktop Services) da Microsoft e as soluções da Citrix para virtualização de desktops têm sido bastante adotados.

Essas soluções permitem que um usuário execute remotamente os programas que necessita, através de um desktop virtual acessível via rede local ou internet. Com isso, o hardware mais robusto fica centralizado num servidor e pode ser compartilhado por diversos usuários simultâneos.

Nesse ambiente, diversos usuários podem estar executando sua aplicação no mesmo computador remoto, compartilhando recursos como a pasta de trabalho, memória mapeada, disco, etc. Desse modo, as diferentes execuções simultâneas do programa podem conflitar entre si. Decisões baseadas no nome do computador, seu endereço IP ou outra informação de hardware também podem causar problemas já que todos os usuários remotos lerão os mesmos valores. A ABC71, por exemplo, usa o nome do computador onde nosso ERP está em execução como parte do controle de licenças.

Em Windows, normalmente as soluções de desktop virtual se baseiam nos serviços de Remote Desktop (antes chamado de Terminal Services), razão pela qual podemos usar a Remote Desktop Services API para preparar nossas aplicações para que rodem nesse ambiente. Essa API permite resgatar e modificar informações sobre um usuário específico, além de executar tarefas relativas à administração do serviço.

Para começar, temos que determinar se o programa está executando num desktop convencional ou num virtual. Isso pode ser conseguido com uma chamada à função GetSystemMetrics. Saber que estou numa sessão remota me permite, por exemplo, ajustar uma pasta de trabalho coerente para cada execução distinta do meu programa.
function TForm1.IsRemoteSession : boolean;
var res : integer;
begin
res := GetSystemMetrics (SM_REMOTESESSION);
Result := (res <> 0);
end;
A função GetSystemMetrics é um curinga da API do Windows; com ela é possível recuperar uma porção de parâmetros do ambiente operacional. Chamada com SM_REMOTESESSION, ela retorna um valor diferente de zero se o programa estiver num desktop virtual.

Muitas informações valiosas sobre a sessão remota em andamento podem ser obtidas através da função WTSQuerySessionInformation. No exemplo abaixo, eu a utilizo para recuperar o nome do computador que está acessando o desktop remoto:
procedure TForm1.GetSessionInfo;
var lSessionId: DWORD;
lBuffer : PChar;
lBytesReturned : DWORD;
lStationName : String;
begin
lSessionId := 0;

{ Descobre a identificação da sessão do usuário com base na identificação do programa no Windows }
if not ProcessIdToSessionId (GetCurrentProcessId (), DWORD(@lSessionId)) then
raise Exception.Create ('Não foi possível obter Remote SessoinId');

lBuffer := Nil;
lBytesReturned := 0;
lStationName := '';

{ Obtem nome da máquina Client }
if (WTSQuerySessionInformation (WTS_CURRENT_SERVER_HANDLE,
lSessionId,
WTSClientName,
lBuffer,
lBytesReturned))
then
lStationName := String(lBuffer)
else
raise Exception.Create ('Não foi possível obter o nome da estação');

{ Libera a memória alocada automaticamente }
WTSFreeMemory (lBuffer);

{ ... }
end;
Vamos por partes. Para recuperar as informações de uma sessão remota, primeiro temos que identificar essa sessão. A função ProcessIdToSessionId da API do Windows nos fornece isso, mapeando o Process ID de nosso programa para a correspondente identificação da sessão remota onde ele está executando.

O passo seguinte é chamar a função WTSQuerySessionInformation para levantar as informações desejadas. Essa função recebe 5 parâmetros. O primeiro é um handle para o servidor da sessão remota. No exemplo, passei a constante WTS_CURRENT_SERVER_HANDLE para indicar que quero informações sobre o servidor atual. Poderia ter usado WTSOpenServer para abrir outro servidor remoto.

O segundo parâmetro é a identificação da sessão que obtivemos no primeiro passo. O parâmetro seguinte é o que determina qual informação será recuperada. Os valores permitidos são os listados no enumerado WTS_INFO_CLASS. No exemplo, usei WTSClientName para obter o nome do computador do usuário que diparou o acesso remoto; a lista de informações recuperáveis inclui o endereço IP desse mesmo computador, a pasta de trabalho remota (no servidor), o nome do usuário e informações sobre o uso da sessão e sobre a Client, dentre outras.

Os dois últimos parâmetros são, respectivamente, um ponteiro para a área que receberá a informação solicitada e a quantidade de bytes que essa informação está ocupando. Como ambos são calculados pela função WTSQuerySessionInformation, não é preciso alocar previamente a memória para eles. No entanto, é nossa responsabilidade liberar a memória alocada usando a função WTSFreeMemory, como mostra o passo final do código do exemplo.

Se tivesse usado GetComputerName, eu obteria o nome do computador remoto, isto é, o servidor onde o programa está efetivamente sendo executado. A questão é que o mesmo nome seria retornado para qualquer usuário que acesse um desktop remoto nesse servidor ...

A lista de funções da Remote Desktop Services API inclui ainda formas de administrar o RDS tais como iniciar e encerrar sessões, inventariar as sessões ativas, modificar configurações de uma sessão, etc.

10 comentários :

Tom disse...

Luís,

Olá. Estou tendo um problema com importação de NFe. Será que você pode me ajudar?

Quando tento importar a NFe recebo a seguinte mensagem:

O conteúdo do elemento 'NFe' não está completo. Um dos seguintes é experado: '{"http://www.w3.org/2000/09/xmldsig#":Signature}'

O código XML tem a seguinte linha:

Sendo que esse domínio não existe mais.

Seria por isso que o problema está ocorrendo? Se sim, qual o novo domínio para colocar nesse campo?

Tom disse...

Faltou a linha. Ela é a seguinte (entre < >)
NFe xmlns="http://www.portalfiscal.inf.br/nfe"
E esse domínio não existe.

Luís Gustavo Fabbro disse...

Tom

Faltou alguma coisa no contexto do seu problema : não sei como vc está tentando importar e o código XML não apareceu no seu comentário ....

Se vc está tentanto bater o XML com um esquema XSD, compare-os pra se há um namespace diferente que tenha que ser aplicado.

No mais, se não conseguir resolver envie o seu xml e ao menos a parte relevante do seu código para o email do balaio.

OBS: Seu comentário não está relacionado com o assunto deste post. Se for necessário, inclua novos comentários no post correto.

[]s

eduardo M. Dantas disse...

Estou usando Delphi 2007 e as funções ProcessIdToSessionId e WTSQuerySessionInformation não são encontradas, procurei na unit Windows e Winsock, mas não encontrei.

Luís Gustavo Fabbro disse...

Eduardo

O Delphi não está trazendo mapeamento para muitas APIs do Windows. Por isso, eu costumo usar os disponibilizados pelo projeto JEDI.

Especificamente as APIs do WTS estão no fonte JwaWtsApi32.pas (neste link). Veja no fonte que há outras units que devem ser copiadas do site pra que tudo compile.

Unknown disse...

Luis me ajuda por favor cara, preciso obter o nome da maquina logada numa sessão de WTS, consigo listar as seções abertas, mas com o seu exemplo tentei obter o nome da maquina e não consegui, da erro de acesso violado, eu ja baixei as units JEDI, mas quando vou receber o nome da maquina, vem varios caracteres e quando jogo num edit da erro, voce teria essa aplicação funcionando?

Luís Gustavo Fabbro disse...

Marcelo

Pela mensagem de erro, é provável que você esteja tentando usar o valor do buffer depois que ele já foi desalocado, isto é, depois do WTSFreeMemory. Jogue esse valor para uma outra memória (controlada por você) antes de liberar o buffer.

Outra possibilidade é o Delphi estar misturando AnsiChar com WideChar. Se for o caso, redeclare o buffer como PAnsiChar.

[]s

Unknown disse...

Luiz só que a função WTSQuerySessionInformation só aceita a variavel de buffer como POinter e não como PChar.

Unknown disse...

Luiz estou tentando pegar o resultado, sempre que atribuou a algum componente de entrada de texto, ou showmessage da erro de acess violation, além de que a variavel
lBuffer : PChar;// só aceita do tipo pointer;

...

Memo1.Lines.Add(lStationName);
WTSFreeMemory (lBuffer);

Unknown disse...

Luis muito obrigado, funcionou cara, era nessa linha
lStationName := String(lBuffer)
troquei "string" por PAnsiChar e funcionou, :), obrigadoooooo!

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.