Message to all users, and their reply

Before you need to reboot a VM, or do some destructive maintenance on there, it is a good practice to at least tell the user(s) of that VM what is going to happen. But how do you address the users of a VM? They can be connected to a console (local) or via a RDP session (remote). And how do you get their reply back?

 

Exactly such a question appeared in the VMTN PowerCLI Community recently. And after some digging, it seems that is possible through a PowerShell script that uses the Remote Desktop Services API, provided through the wtsapi32.dll. Note that the VMs we are looking at, all are running a Windows guest OS.

Background

After using some Google-fu I found some examples of scripts that use the wtsapi32.dll to find users connected to a station. There is a script called Send-TSMessageBox that shows how you can send a messagebox to the users. And there is this reddit thread called WTSEnumerateSessions, that contains most of the logic I used in my script. So thanks to geostude and RowdyBullGaming for their code.

That was part 1 of solving the question.

The second part was that I couldn’t get my code to work. I was using Invoke-VMScript to run the script on the target VM, but I kept hitting inexplicable errors. Then I finally realised that I was hitting the maximum ScriptText length. Unfortunately my Invoke-VMScriptPlus function, which does not suffer from that ScriptText length problem, did not work for VMs with a Windows guest OS.

As a fix I started writing Invoke-VMScriptPlus V2, which handled sending scripts to VMs with a Windows guest OS.

And finally I got the message box working!

The Code

Annotations

Line 1: The actual PowerShell script we will run inside the guest OS, is presented as a here-string

Line 4: We are using a MessageBox with three buttons: Yes, No and Cancel. Depending on which button the user selected, a different value will be returned.

Line 5,6: Since a here-string is just that, a string, we can find and replace specific patterns in there. We use the patterns #timeMin# and #timeSec# to fill in the actual time.

Line 8-74: To be able to use the Remote Desktop Services API methods, we have to declare the CSharp code with the Add-Type cmdlet. Note that CSharp is the default value for the Language parameter on the Add-Type cmdlet.

Line 79: Since we are running the code inside the VM’s guest OS, we use localhost as the servername

Line 80: We use the WTSEnumerateSessions method to find out how many sessions are present. On a side note, whenever you are looking for sample snippets for specific API methods, always have a look at the pInvoke.Net website, they have tons of sample snippets, including PowerShell code. Have for example a look at WTSEnumerateSessions.

Line 86: We are only interested in ‘active’ sessions.

Line 88: This line will send the actual MessageBox to the session, and wait for the reply, or until the timeout expires. That means that if you have multiple sessions ope to the same VM, that the total time before the script comes back, will be #number-of-sessions X #timeout value. This is a drawback of the script that I haven’t been able yet to resolve. A possibility I’m looking at is to send the MessageBox through a Start-Job to each active session.

Line 94: The responses for each active session are returned

Sample Use

Take note that there are always sessions present, the MessageBox only needs to be displayed in active sessions.   The following shows the sessions that are present when there is one active RDP session. Those are the ones that have the WTSActive state.

When we run the script on a target VM, the following MessageBox is displayed to the user of each active session.

The user can press any of the three available buttons, or the MessageBox can just time out. Depending on what happens, a different value will be returned. 

Action Value
Cancel button 3
Yes button 6
No button 7
Timeout 32000

Once you have this value the corresponding action can be taken.
The MessageBox works for RDP sessions as well as for local sessions, as the following two partial screenshots demonstrate.

Local

RDP

Is this the ideal solution for the original question?
As long as there is only one session to a VM, then yes. If there are multiple sessions, the scripts needs more work to cope with that. Suggestions and improvements are always welcome.

Enjoy!

5 Comments

    Jim

    A lot of work. Why so many compiles.

    The API has been wrapped and fully decoded – see PowerShellGet for versions.

    Why not use the following:

    msg * /Server:TsServer1 /Time:15 ‘This server will reboot in 14 seconds’

      LucD

      Well, there are a couple of reasons to not use msg.

      • The target stations are not reachable from where you are sitting
      • You want richer feedback from the user than an acknowledgement

      Krish MD

      Thanks very much for sharing. Something I was looking for as well.

      Lex van der Horst

      Thanks for this nice article, will check it out !

      Chetna Tanwani

      Great idea to poll the End user views before actually taking the action. Thanks for sharing!

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    *
    *

    This site uses Akismet to reduce spam. Learn how your comment data is processed.