Link to home
Start Free TrialLog in
Avatar of fruitloopy
fruitloopy

asked on

VB.NET - How to use WTSSendMessage to send messages to users

I am developing an application that members of desktop support use to perform certain support functions. One of these is to send a popup message to a user on the network.

Having found out that sending messages using the msg.exe thats built in to Vista and above is not possible VB.NET in Visual Studio I have learnt that an API call to WTSSENDMESSAGE is what I need.
However, I cannot find any useful code I can use to acheive this, coupled with the fact that I am not a programmer. I just find code that works and change it to my needs.

The best info I could find for this is:

Declare Function WTSSendMessage Lib "wtsapi32.dll" ( _
	ByVaL hServer As Long, _
	ByVaL SessionId As Long, _
	ByVaL pTitle As String, _
	ByVaL TitleLength As Long, _
	ByVaL pMessage As String, _
	ByVaL MessageLength As Long, _
	ByVaL Style As Long, _
	ByVaL timeout As Long, _
	ByRef pResponse As Long, _
	ByVaL bWait As Long) As Long

Open in new window

And this link has details and examples in C++
WTSSENDMESSAGE

Can anyone turn this into something useful? I will have to textboxes in the form called:
txtPCName
txtMessage
With a button that sends it:
btnSend

Many thanks
Avatar of Bob Learned
Bob Learned
Flag of United States of America image

This may or may not be helpful:

wtssendmessage (wtsapi32)
http://www.pinvoke.net/default.aspx/wtsapi32/wtssendmessage.html
Avatar of fruitloopy
fruitloopy

ASKER

Thanks TheLearnedOne. I have already read that but I just don't know how to put it into useful code.
I am new to VB.NET.
Here is some sample code in VB.NET taken from that p/invoke topic:


Public Class MessageService

Public Shared Function SendMessage(title As String, message As String, ByRef errorCode As String) As Integer
	Dim WTS_CURRENT_SERVER_HANDLE As IntPtr = IntPtr.Zero
	Dim WTS_CURRENT_SESSION As Integer = -1

	Dim tlen As Integer = title.Length
	Dim mlen As Integer = msg.Length
	Dim response As Integer = 0
	Dim result As Boolean = WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, title, tlen, message, mlen, _
		0, 0, resp, False)
	Dim err As Integer = Marshal.GetLastWin32Error()
	Return response
End Function

End Class 

Open in new window


Example call:
Dim response As Integer = MessageService.SendMessage("Hello Kitty", "My name is Bob")
That looks great, thank TheLearnedOne!

It produces an error:
Error      4      Argument not specified for parameter 'errorCode' of 'Public Shared Function SendMessage(title As String, message As String, ByRef errorCode As String) As Integer'.

For this line:
Dim response As Integer = MessageService.SendMessage("Hello Kitty", "My name is Bob")

Here's my full code
    Private Sub btnSend_Click(sender As Object, e As EventArgs) Handles btnSend.Click
        If Main.Reachable(txtPCName.Text) Then
            Dim MyReg As Microsoft.Win32.RegistryKey = Microsoft.Win32.RegistryKey.OpenRemoteBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, txtPCName.Text)
            Dim MyRegKey As Microsoft.Win32.RegistryKey
            MyRegKey = MyReg.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System", True)
            MyRegKey.SetValue("PromptOnSecureDesktop", 1, Microsoft.Win32.RegistryValueKind.DWord)
            MyRegKey = MyReg.OpenSubKey("SYSTEM\CurrentControlSet\Control\Terminal Server", True)
            MyRegKey.SetValue("AllowRemoteRPC", 1, Microsoft.Win32.RegistryValueKind.DWord)
            MyRegKey.Close()
        End If
        'Dim Message = txtMessage.Text
        Dim result = MessageBox.Show("The computer " & txtPCName.Text & " will be sent the message you typed, are you sure you want to do this?", "Confirmation", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
        If result = Windows.Forms.DialogResult.Yes Then
            Dim response As Integer = MessageService.SendMessage("Hello Kitty", "My name is Bob")
        ElseIf result = Windows.Forms.DialogResult.No Then
            Dim MyReg As Microsoft.Win32.RegistryKey = Microsoft.Win32.RegistryKey.OpenRemoteBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, txtPCName.Text)
            Dim MyRegKey As Microsoft.Win32.RegistryKey
            MyRegKey = MyReg.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System", True)
            MyRegKey.SetValue("PromptOnSecureDesktop", 0, Microsoft.Win32.RegistryValueKind.DWord)
            MyRegKey = MyReg.OpenSubKey("SYSTEM\CurrentControlSet\Control\Terminal Server", True)
            MyRegKey.SetValue("AllowRemoteRPC", 0, Microsoft.Win32.RegistryValueKind.DWord)
            MyRegKey.Close()
        End If
    End Sub

    Public Class MessageService
        Public Shared Function SendMessage(title As String, message As String, ByRef errorCode As String) As Integer
            Dim WTS_CURRENT_SERVER_HANDLE As IntPtr = IntPtr.Zero
            Dim WTS_CURRENT_SESSION As Integer = -1
            Dim tlen As Integer = title.Length
            Dim mlen As Integer = message.Length
            Dim response As Integer = 0
            Dim result As Boolean = WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, title, tlen, message, mlen, _
                0, 0, response, False)
            Dim err As Integer = Marshal.GetLastWin32Error()
            Return response
        End Function
    End Class
End Class

Open in new window


I realise I still may not be calling it correctly but I just cant figure out what's missing.
I cannot see how I can send this to a remote PC either. Sorry!
ASKER CERTIFIED SOLUTION
Avatar of Bob Learned
Bob Learned
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
This was all far to complex to implement so I took a different direction. The msg.exe must be run with elevated rights so I used a handy little elevate64.exe file to elevate the  msg.exe and then pass commands to it.
First I have to check if the remote registry service is running before changing two required registry values. If the registry values are not changed it will not display the message on the remote PC.

Here's the code if anyone wants to use it:
Imports System.Net.NetworkInformation
Imports System.Net
Imports System.Threading
Imports System.Text
Imports System.Diagnostics
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Imports System.Management
Imports System.ServiceProcess

Public Class Message

    Private Sub Message_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        txtPCName.Text = Main.txtHostName.Text()
    End Sub
    ' Is the service running?
    ' Returns true if we need to start the service
    Public Function CheckService(ByVal PC As String) As Boolean
        Dim obj As ManagementObject

        obj = New ManagementObject("\\" & txtPCName.Text & "\root\cimv2:Win32_Service.Name='RemoteRegistry'")
        If Not IsNothing(obj) Then
            If obj("StartMode").ToString <> "Disabled" And obj("State").ToString = "Running" Then
                Return False
            End If
        End If
        Return True
    End Function
    Public Sub StartService(ByVal PC As String)
        Dim obj As ManagementObject
        'Dim inParams, outParams As ManagementBaseObject
        'Dim Result As Integer
        Dim sc As New ServiceController

        obj = New ManagementObject("\\" & txtPCName.Text & "\root\cimv2:Win32_Service.Name='RemoteRegistry'")
        ' Start the service
        If obj("State").ToString <> "Running" Then
            ' now start the service
            sc = New ServiceController("RemoteRegistry", PC)
            sc.Start()
            sc.WaitForStatus(ServiceControllerStatus.Running)
        End If
    End Sub

    Private Sub btnSend_Click(sender As Object, e As EventArgs) Handles btnSend.Click
        If Main.Reachable(txtPCName.Text) Then
            CheckService(txtPCName.Text)
            If CheckService(txtPCName.Text) = True Then
                StartService(txtPCName.Text)
            End If
            Dim MyReg As Microsoft.Win32.RegistryKey = Microsoft.Win32.RegistryKey.OpenRemoteBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, txtPCName.Text)
            Dim MyRegKey As Microsoft.Win32.RegistryKey
            MyRegKey = MyReg.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System", True)
            MyRegKey.SetValue("PromptOnSecureDesktop", 1, Microsoft.Win32.RegistryValueKind.DWord)
            MyRegKey = MyReg.OpenSubKey("SYSTEM\CurrentControlSet\Control\Terminal Server", True)
            MyRegKey.SetValue("AllowRemoteRPC", 1, Microsoft.Win32.RegistryValueKind.DWord)
            MyRegKey.Close()


        End If
        Dim result = MessageBox.Show("The computer " & txtPCName.Text & " will be sent the message you typed, are you sure you want to do this?", "Confirmation", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
        If result = Windows.Forms.DialogResult.Yes Then
            Dim SendMessage As New ProcessStartInfo
            SendMessage.WindowStyle = ProcessWindowStyle.Hidden
            SendMessage.CreateNoWindow = True
            SendMessage.UseShellExecute = False
            SendMessage.FileName = """C:\Temp\Elevate64.exe"""
            SendMessage.Arguments = " msg.exe /server:" & txtPCName.Text & " /time:0" & " * " & txtMessage.Text
            Process.Start(SendMessage)
            MsgBox("Your message has been sent")
        ElseIf result = Windows.Forms.DialogResult.No Then
            Dim MyReg As Microsoft.Win32.RegistryKey = Microsoft.Win32.RegistryKey.OpenRemoteBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, txtPCName.Text)
            Dim MyRegKey As Microsoft.Win32.RegistryKey
            MyRegKey = MyReg.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System", True)
            MyRegKey.SetValue("PromptOnSecureDesktop", 0, Microsoft.Win32.RegistryValueKind.DWord)
            MyRegKey = MyReg.OpenSubKey("SYSTEM\CurrentControlSet\Control\Terminal Server", True)
            MyRegKey.SetValue("AllowRemoteRPC", 0, Microsoft.Win32.RegistryValueKind.DWord)
            MyRegKey.Close()
        End If
    End Sub

    Private Sub btnClear_Click(sender As Object, e As EventArgs) Handles btnClear.Click
        txtMessage.Text = ""
        txtMessage.Focus()
    End Sub
End Class

Open in new window