-1

I want a powershell script running as SYSTEM user to display a Windowsform on another users session and have interaction with the controls of it.

I am trying to automate the installation/repair of Symantec Endpoint Protection with Solarwinds N-Able. This platform uses agent software which is installed on clients to monitor and execute tasks on them.

The agent uses the NT AUTHORITY\SYSTEM user to execute tasks on the machine. The installation of SEP works fine so far, but the reboots in between the deinstall/install phases are still uncontrollable as a regular user on the machine. I want the currently active user be able to control this reboot cycles. Something like the Windows update reboot prompt.

My idea is to display a windowsform on logged on user's desktop with controls on it to execute or delay the reboot. My question now is how do I display a windowsform defined in powershell on another user's session, and how am I going to get the actions of the controls back in the script that is running on the SYSTEM user.

I've already tried the msg command to send a message to all the users on the system. But this is only one-way communication and isn't really meant to be used in situations like this is guess.

VVG
  • 41
  • 4
  • 1
    Do some reading up on Powershell, make a start, get some code, paste what doesn't work here then people can help. What you want is probably scriptable but SO is not a script writing service. – Scepticalist May 21 '19 at 13:43
  • 1
    This is the kind of thing that you're going to have a hard time doing in PowerShell. Cross-session communication is tricky enough from regular processes, let alone PowerShell. It boils down to having to run code in another session altogether; you cannot remotely puppeteer the UI because that's exactly what the session divide prevents. – Jeroen Mostert May 21 '19 at 13:52
  • You can get limited two-way communication with the [WTSSendMessage](https://learn.microsoft.com/en-us/windows/desktop/api/wtsapi32/nf-wtsapi32-wtssendmessagea) function, but it doesn't allow a custom UI. A [previous answer of mine](https://stackoverflow.com/questions/54357542/msgbox-in-powershell-script-run-from-task-scheduler-not-working/54362903#54362903) shows how to use it in PowerShell. – boxdog May 21 '19 at 14:12
  • @Scepticalist Thanks for your advise, this is my very first post on SO. I will try to be more specific and have examples in future posts. – VVG May 21 '19 at 14:27

1 Answers1

4

I found the solution for my problem. I used the WTSSendMessage function which boxdog suggested in the comments. I combined this with a script that gets the sessionID's of the logged on users. I minimized this script to only get the "Active" user's sessionID. This is then used to send the message to user. I tested it in Solarwinds and so far this works flawless.

My coding skills are pretty basic, but this is the end result.

function Send-MessageBox
{
[CmdletBinding()]
[OutputType([string])]
Param
(        
    [Parameter(Mandatory=$true, Position=0)]
    [string]$title,
    [Parameter(Mandatory=$true, Position=1)]
    [string]$message,
    [Parameter(Mandatory=$true, Position=2)]
    [int]$duration,
    [Parameter(Mandatory=$true, Position=3)]
    [int]$style
)

Begin
{
    $typeDefinition = @"
        using System;
        using System.Runtime.InteropServices;

        public class WTSMessage {
            [DllImport("wtsapi32.dll", SetLastError = true)]
            public static extern bool WTSSendMessage(
                IntPtr hServer,
                [MarshalAs(UnmanagedType.I4)] int SessionId,
                String pTitle,
                [MarshalAs(UnmanagedType.U4)] int TitleLength,
                String pMessage,
                [MarshalAs(UnmanagedType.U4)] int MessageLength,
                [MarshalAs(UnmanagedType.U4)] int Style,
                [MarshalAs(UnmanagedType.U4)] int Timeout,
                [MarshalAs(UnmanagedType.U4)] out int pResponse,
                bool bWait
            );

            static int response = 0;

            public static int SendMessage(int SessionID, String Title, String Message, int Timeout, int MessageBoxType) {
                WTSSendMessage(IntPtr.Zero, SessionID, Title, Title.Length, Message, Message.Length, MessageBoxType, Timeout, out response, true);

                return response;
            }
        }
"@
}

Process
{
    if (-not ([System.Management.Automation.PSTypeName]'WTSMessage').Type)
    {
        Add-Type -TypeDefinition $typeDefinition
    }

    $RawOuput = (quser) -replace '\s{2,}', ',' | ConvertFrom-Csv
    $sessionID = $null

    Foreach ($session in $RawOuput) {  
        if(($session.sessionname -notlike "console") -AND ($session.sessionname -notlike "rdp-tcp*")) {
            if($session.ID -eq "Active"){    
                $sessionID = $session.SESSIONNAME
            }                        
        }else{
            if($session.STATE -eq "Active"){      
                $sessionID = $session.ID
            }        
        }   
    }
    $response = [WTSMessage]::SendMessage($sessionID, $title, $message, $duration, $style )
}
End
{
    Return $response
}
}

Send-MessageBox -title "Title" -message "Message" -duration 60 -style 0x00001034L
VVG
  • 41
  • 4