VB .NET - Saindo do Windows e Fazendo o Logoff
Em certas ocasiões, após realizar determinada atualização, é necessário que o sistema seja inicializado e o usuário faça o log-off para realizar um novo login e assim acessar o sistema atualizado.
Este artigo vai mostrar como utilizar a API do Windows para sair do Windows e realizar o log-off do usuário.
A função ExitWindowsEx permite que uma mensagem seja enviada a todos os processos em execução para o usuário atual, solicitando que eles terminem de forma a permitir que o computador seja desligado, reiniciado ou fique em preparação para o usuário atual ser desconectado.
Esta função desconecta o usuário interativo, desliga o sistema, ou desliga e reinicia o sistema. Ela envia a mensagem WM_QUERYENDSESSION para todas as aplicações para determinar se elas podem ser encerradas.
A chamada da API é usada para terminar todos os processos do usuário atual. Quando o usuário que executa a função é o usuário interativo, isso também pode fazer com que o sistema automaticamente desligue ou reinicie.
Quando o comando é chamado de um serviço que está sendo executado sob as credenciais de um usuário diferente ou dentro de uma sessão do Terminal Services, a função pode parar com sucesso todos os processos atuais sem iniciar um desligamento do servidor físico. (Nesta situação, se um desligamento é necessária a API InitiateSystemShutdownEx pode ser utilizada.)
Criando um projeto VB .NET
Vamos então criar um programa VB .NET que demonstra como desligar e/ou reiniciar o sistema via código.
Abra o Visual Basic 2010 Express Edition e crie um novo projeto do tipo Windows Forms Applcaction chamado "ShutdownVBNET".
No formulário padrão form1.vb adicione três botões de comando conforme o leiaute abaixo:
Controles Button:
|
O .NET Framework não provê nativamente qualquer meio de desligar o sistema. Em vez disso, você deve usar uma função da API do Windows para esta tarefa. A primeira coisa que precisamos fazer é referenciar o namespace InteropServices para que possamos acessar as funções da API do Windows. Para fazer isso, adicione uma nova diretiva no início do formulário
Imports System.Runtime.InteropServices
Devemos também declarar o seguinte namespace:
Imports System.ComponentModel
Uma vez que a referência ao namespace InteropServices foi adicionada, podemos declarar a função da API como uma função estática de uma classe ou no formulário conforme a seguir:
<DllImport("user32.dll", SetLastError:=True)> _ Private Shared Function ExitWindowsEx(ByVal uFlags As UInteger, ByVal dwReason As UInteger) As Integer End Function |
A função ExitWindowsEx utiliza dois parâmetros :
Vamos incluir o código abaixo que define uma enumeração com os flags usados:
Enum ExitFlags
Logoff = 0
Shutdown = 1
Reboot = 2
Force = 4
PowerOff = 8
ForceIfHung = 16
End Enum
Enum Motivo As UInteger ApplicationIssue = &H40000 HardwareIssue = &H10000 SoftwareIssue = &H30000 PlannedShutdown = &H80000000UI End Enum |
Elevando os Previlégios
Todos os programas são executados com um conjunto de privilégios que habilita e desabilita a funcionalidade do Windows. Por motivos de segurança os privilégios previstos em um programa devem ser o mínimo necessário para garantir a execução correta. Por este motivo, a capacidade de desligar o Windows não é fornecido como padrão para uma aplicação. O privilégio deve ser solicitado antes do comando de desligamento estar disponível.
Elevar os privilégios do programa para incluir o requerido direito de ShutDown usa uma série de chamadas de API. Para isso as seguintes funções são utilizadas:
Vamos criar um novo método para o nosso exemplo que atribui o privilégio para o processo atual antes de tentar executar qualquer procedimento de shutdown.
Antes de criar o método temos que declarar as 4 chamadas à API. Essas chamadas usam alguns valores constantes e uma estrutura.
Abaixo temos estas declarações no formulário form1.vb:
Const PrivilegeEnabled As Integer = &H2 Const TokenQuery As Integer = &H8 Const AdjustPrivileges As Integer = &H20 Const ShutdownPrivilege As String = "SeShutdownPrivilege" <StructLayout(LayoutKind.Sequential, Pack := 1)> _ Friend Structure TokenPrivileges Public PrivilegeCount As Integer Public Luid As Long Public Attributes As Integer End Structure <DllImport("kernel32.dll")> _ Friend Shared Function GetCurrentProcess() As IntPtr End Function <DllImport("advapi32.dll", SetLastError := True)> _ Friend Shared Function OpenProcessToken(processHandle As IntPtr, desiredAccess As Integer, ByRef tokenHandle As IntPtr) As Integer End Function <DllImport("advapi32.dll", SetLastError := True)> _ Friend Shared Function LookupPrivilegeValue(systemName As String, name As String, ByRef luid As Long) As Integer End Function <DllImport("advapi32.dll", SetLastError := True)> _ Friend Shared Function AdjustTokenPrivileges(tokenHandle As IntPtr, disableAllPrivileges As Boolean, ByRef newState As TokenPrivileges, bufferLength As Integer, previousState As IntPtr, length As IntPtr) As Integer End Function |
Feitas as declarações o novo método pode ser adicionado ao código. Este método usa as 4 funções da API para identificar o processo atual, recuperar os privilégios atuais, identificar o privilégio de desligamento e concedê-lo para o usuário. Adicione o seguinte método no código do formulário:
Private Sub ElevarPrevilegios() Dim currentProcess As IntPtr = GetCurrentProcess() Dim tokenHandle As IntPtr = IntPtr.Zero Dim result As Integer = OpenProcessToken(currentProcess, AdjustPrivileges Or TokenQuery, tokenHandle) If result = 0 Then Throw New Win32Exception(Marshal.GetLastWin32Error()) End If Dim tokenPrivileges As TokenPrivileges tokenPrivileges.PrivilegeCount = 1 tokenPrivileges.Luid = 0 tokenPrivileges.Attributes = PrivilegeEnabled result = LookupPrivilegeValue(Nothing, ShutdownPrivilege, tokenPrivileges.Luid) If result = 0 Then Throw New Win32Exception(Marshal.GetLastWin32Error()) End If result = AdjustTokenPrivileges(tokenHandle, False, tokenPrivileges, 0, IntPtr.Zero, IntPtr.Zero) If result = 0 Then Throw New Win32Exception(Marshal.GetLastWin32Error()) End If End Sub |
Saindo do Windows
Com a API referenciada, os códigos das enumerações flag, os motivos disponíveis e um método que eleva os privilégios do programa para permitir shutdowns definidos, podemos adicionar a funcionalidade aos botões de comando do formulário para demonstrar o uso do método ExitWindowsEx.
Quando a função ExitWindowsEx é chamada, ela retorna um valor inteiro indicando o sucesso ou fracasso. Se a chamada falhar, o valor de retorno é zero e os detalhes do erro são armazenados. O erro pode ser recuperado usando o método GetLastWin32Error da classe Marshal. Embora este método retorne um número inteiro, os detalhes da exceção podem ser vistos lançando a exceção Win32Exception e passando o número de erro como um parâmetro.
Para adicionar o shutdown e o código de detecção de erros para o botão ShutDown, adicione o seguinte código ao evento Click do botão. Esse exemplo combina dois códigos de motivo para indicar ao sistema operacional que o sistema foi desligado devido a um problema de hardware, mas que isso foi planejado:
Private Sub btnShutDown_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnShutDown.Click ElevarPrevilegios() Dim result As Integer = ExitWindowsEx(CUInt(ExitFlags.Shutdown Or ExitFlags.PowerOff), CUInt(Motivo.HardwareIssue Or Motivo.PlannedShutdown)) If result = 0 Then Throw New Win32Exception(Marshal.GetLastWin32Error()) End If End Sub |
Para testar podemos executar o projeto e clicar no botão ShutDown.(Lembre-se de salvar o seu trabalho)
Forçando o ShutDown
O procedimento de desligamento descrito acima envia uma mensagem para todos os processos do usuário atual, solicitando que eles sejam fechados. Isto é semelhante a fechar o Windows através do menu Iniciar. Cada programa que está sendo executado irá fechar normalmente, dando ao usuário a opção de salvar qualquer trabalho aberto sempre que adequado. Se qualquer um dos softwares em execução não fechar dentro de um período de tempo limite, ao usuário é dada a opção de encerrar os programas à força ou cancelar o procedimento de desligamento.
Em alguns casos de emergência, como quando os programas travam, você pode querer forçar o sistema a fechar todos os processos ativos. Ao incluir a flag Force, todos os processos são obrigados a fechar e ao usuário não é dada a opção de cancelar o processo. A Flag ForceIfHung também pode ser usada para forçar as aplicações a encerrar se elas não responderem dentro do tempo limite. Qualquer opção deve ser usada com cuidado, pois elas podem fechar aplicativos sem dar ao usuário a opção de salvar seu trabalho em primeiro lugar.
Para forçar um desligamento, ajuste o código do evento Click do botão ShutDown conforme abaixo:
Private Sub btnShutDown_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnShutDown.Click ElevarPrevilegios() Dim result As Integer = ExitWindowsEx(CUInt(ExitFlags.Shutdown Or ExitFlags.PowerOff Or ExitFlags.Force), CUInt(Motivo.HardwareIssue Or Motivo.PlannedShutdown)) If result = 0 Then Throw New Win32Exception(Marshal.GetLastWin32Error()) End If End Sub |
Reiniciando o Windows
Se a sua aplicação fez alterações na configuração do Windows, você pode exigir que o sistema operacional seja reiniciado em vez de simplesmente ser desligado.
Isto é feito através da modificação do parâmetro flag da função ExitWindowsEx, de modo que ela use a flag Reboot, em vez das diversas flags ShutDown. A flag Force pode também ser combinada com a flag Reboot.
Para adicionar o código de reinício para o aplicativo exemplo, inclua o código abaixo no evento CLick do botão Reiniciar:
Private Sub btnReiniciar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnReiniciar.Click ElevarPrevilegios() Dim result As Integer = ExitWindowsEx(CUInt(ExitFlags.Reboot), CUInt(Motivo.SoftwareIssue Or Motivo.PlannedShutdown)) If result = 0 Then Throw New Win32Exception(Marshal.GetLastWin32Error()) End If End Sub |
Fazendo o Logoff
O último botão do nosso exemplo será usado apenas para dar o log-off no usuário do Windows. Isto é útil para alterações de configuração menores onde uma reinicialização total não é necessário. Mais uma vez, as flags da função ExitWindowsEx são a única alteração necessária.
Para completar o programa de exemplo, adicionar o código abaixo no evento Click do botão Logoff:
Private Sub btnLogoff_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnLogoff.Click ElevarPrevilegios() Dim result As Integer = ExitWindowsEx(CUInt(ExitFlags.Logoff), CUInt(Motivo.ApplicationIssue Or Motivo.PlannedShutdown)) If result = 0 Then Throw New Win32Exception(Marshal.GetLastWin32Error()) End If End Sub |
Assim completamos as funcionalidades que permitem a nossa aplicação VB .NET utilizar a função ExitWindowsEx para realizar a reinicialização, o desligamento e o log-off do usuário.
Pegue o projeto completo aqui: ShutDownVBNET.zip
1Jo 2:1
Meus filhinhos, estas coisas vos escrevo, para que não pequeis; mas, se alguém pecar, temos um Advogado para com o Pai, Jesus Cristo, o justo.1Jo 2:2
E ele é a propiciação pelos nossos pecados, e não somente pelos nossos, mas também pelos de todo o mundo.1Jo 2:3
E nisto sabemos que o conhecemos; se guardamos os seus mandamentos.Referências: