#PowerShell message box while being ran as a service

216 messages · Page 1 of 1 (latest)

royal vortex
#

Hi guys! I am in need of some help and before posting my code, I am not a PS coder, I am able to understand the code well, but most of it is AI generated, tested and after adjusted to my needs. I use NSSM to run a Batch file as a service that starts a PowerShell file that at some point is going to display a message box, however since it's being started from a no UI window, the message box pretty much never displayed. I got it to work after some time, but currently I have a problem that when it's ran as a service, half of the message box's content just get's straight up cut off.

The msgbox content is like A threat was found in {filename} (PID: {pid}) accesing unknow and then it doesn't print any of the other stuff that it's supposed to. The second line and reminder are totally skipped. Any ideas how to fix this issue? Thanks!

tiny quartz
#

Well write the msgbox data to a file.

#

You can’t expect gui elements from a service. What account is the service running under too?

lusty parcel
#

You'd have to run something in the users interactive session, start a different process.

A scheduled task could be used to do that so it's certainly possible. I'd be inclined to look and see if BurntToast does this kind of thing too.

royal vortex
#

If I ran normally from command line, it would work just fine, but as soon as it's ran by service it wouldn't show

tiny quartz
# royal vortex NT AUTHORITY\SYSTEM

yeah so that isn't your local user right so you wouldn't see it and i don't know if there is any scenario where you can get a pop-up gui msg from a service.

tiny quartz
lusty parcel
#

it's where you'd start hitting WCF if it were something real. A dirty hack would be that scheduled task in the users interactive process. Rubbish security boundary around the service though.

#

but then it's running as SYSTEM, so...

royal vortex
royal vortex
tiny quartz
tiny quartz
royal vortex
#

what logging data is supposed to be written into a file?

tiny quartz
tiny quartz
royal vortex
tiny quartz
tiny quartz
lusty parcel
#

windows communication foundation

royal vortex
tiny quartz
royal vortex
tiny quartz
royal vortex
#

the file monitors WMI for new processes, they are after checked for digital signature by sigcheck, if unsigned they are checked for virustotal verdicts, if above 0, they are suspended and that's when the msgbox asks them if they would like to allow/terminate the process

#

It all works perfect currently, but the problem is

   $line2 = "Detection $Ratio percent ($Detections of $TotalAVs)"
   $reminder = "`r`nYes=Allow  No=Terminate  Cancel=Exclude"```
displays pretty much only line 1, no line2/reminder
tiny quartz
tiny quartz
tiny quartz
#

i think you are getting into wrong tools for the job territory.

royal vortex
# tiny quartz this doesn't really tell me how it is written to the msgbox or screen.
# ==============================
# WTSSendMessage Implementation
# ==============================
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;

public class WtsUtils
{
    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern uint WTSGetActiveConsoleSessionId();

    [DllImport("wtsapi32.dll", SetLastError=true, CharSet=CharSet.Auto)]
    public static extern bool WTSSendMessage(
        IntPtr hServer,
        int SessionId,
        string pTitle,
        int TitleLength,
        string pMessage,
        int MessageLength,
        int Style,
        int Timeout,
        out int pResponse,
        bool bWait
    );
}
"@

function Get-ActiveSessionId {
    $sessionId = [WtsUtils]::WTSGetActiveConsoleSessionId()
    if ($sessionId -eq 0xFFFFFFFF) {
        Write-Warning "No active console session found"
        return $null
    }
    return [int]$sessionId
}
˙```
#

Show-ThreatDialog -FileName $processName ` -ProcessId $processId ` -AccessedProcess "unknown" ` -Ratio $ratio ` -Detections $($vtResult.Detections) ` -TotalAVs $($vtResult.TotalAVs) ` -ExecutablePath $executablePath

tiny quartz
royal vortex
royal vortex
tiny quartz
#

i mean this is on the tip of my knowledge so maybe someone with more knowledge of these win32 apis can come in and help. there are just many layers to this. nssm and the bat file and the c# and you are only giving fragments of what you have.

royal vortex
#

however I still don't understand why is only half of the messagebox shown

#

I probably would just go with the half msgbox instead of digging in this deeper

tiny quartz
#

it is the full msgbox outside the service?

tiny quartz
#

have you tried running the service as your own account?

royal vortex
tiny quartz
#

now maybe WTSGetActiveConsoleSessionId does what i am talking about.

#

learned something new. apologies if I came off rude. was just trying to help ultimately and learn something.

royal vortex
royal vortex
#

regardless of what I was doing before, why is this limited to 9 characters in the title?

tiny quartz
#

it is a good question.

#

Doesn't seem like it would matter because one char can be one byte but I'd convert to byte array and then count that.

royal vortex
#
        $fullMessage   = "$FileName Detection: $Ratio ($Detections/$TotalAVs) ------------------------------------------------------------------"```
#

if I do this, it's able to display around 20 chars each

#

which is pretty weird

#

@tiny quartz nah ahahah this is so weird, the longer the title is the longer it allows the message itself to be

tiny quartz
#

oh yeah so it is related to the size of the box. You have to pad the title it seems and muck around with it. feature not a bug. 😉

quick elk
#

There’s a PS module called AnyBox that can be useful for message boxes and user prompts/interactions if that’s what you mean

#

But idk anything about running things as services and stuff so probably not gonna be helpful to you in this case lol

royal vortex
quick elk
#

Good luck with your project if it wasn’t already solved

royal vortex
royal vortex
#

@lusty parcel @tiny quartz after the hassle, I decided to try the scheduled task running with interactive mode and that indeed seems like the best idea. Thanks for taking the time and research!

woeful shadow
#

This post has the right code for WTSSendMessage

woeful shadow
#

Oh, you got the types wrong in your DLLImport definition, you need some MarshalAs help

woeful shadow
#

That one fixes your cutoff problem

#

But I'm having a quiet, boring Saturday, so I got a little carried away

tiny quartz
#

nice. thanks for checking in on that . interesting.

tiny quartz
woeful shadow
#

Honestly, two things ...

#
  1. The error was obviously because things weren't marshalling properly
#
  1. I searched for PInvoke WTSSendMessage and found the article I linked earlier, I assumed Luc didn't blog it if it didn't work, so I just copied his signature for the method, and it worked.
tiny quartz
tiny quartz
hearty oyster
#

Depends, I'm usually explicit when it comes to string parameters because trying to understand the default logic does not pass the 2am test

tiny quartz
#

I assume ctypes takes it into account too?

hearty oyster
#

The new LibraryImport generator now requires you to explicitly tell it what string marshaling method to use which is nice

#

Basically what I do with PInvoke defs

  • Make sure I specify the W or A suffix on the function name to be explicit in what I want to call
  • Make sure CharSet is to set Unicode or Ansi to be explicit
#

and in that I 99% of the time favour the W APIs and a Unicode CharSet

woeful shadow
hearty oyster
#

pinvoke.net just has a lot of sub-optimal code. Because it's a wiki people of various skill levels have contributed to it and thus has various levels of code quality

tiny quartz
hearty oyster
woeful shadow
hearty oyster
#

Plus I dont know how a site like this still hasn't fixed up that sidebar problems, it's been there for years

tiny quartz
woeful shadow
hearty oyster
#

Yep, IntPtr is blittable but using that with string parameters means you need to manually handle the copies and marshaling

tiny quartz
tiny quartz
hearty oyster
#

Using IntPtr requires you to manually marshal things

tiny quartz
hearty oyster
#

While strings are not blittable (without using "unsafe" code), relying on .NET to marshal them is a lot easier than doing it yourself which means annotating with MarshalAs or using the CharSet field in DllImport

#

Blittability comes from how the managed type's memory of that value is the same in unmanaged land, for example an int in C# is 4 bytes of data which is the same as most ints in C functions so you can safely pass it using pointers. Whereas other types you typically need to copy it from a managed object to the raw un managed bytes and provide a pointer to the functions

tiny quartz
#

and also, technically, this definition can be done more explicitly?

        [DllImport("Wtsapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
         private static extern bool WTSEnumerateSessionsExW(
             IntPtr hServer,
             ref int pLevel,
             int Filter,
             out IntPtr ppSessionInfo,
             out int pCount);
hearty oyster
#

Basically when it is blittable C# can just pass the pointer to the internal memory that backs the value

tiny quartz
hearty oyster
#

Yea you can, I typically only do so if I know the C type is a different size than the C# type I specified it as

tiny quartz
hearty oyster
#

For strings, blittability doesn't really come into play (at least not without "unsafe" code), it's more a question of how C# passes that string value to the underlying C call

woeful shadow
#

do you actually need the "Ex" version of that?

tiny quartz
hearty oyster
#

I think it's more a question does the Ex function expose extra functionality you can't get out of the non Ex one

tiny quartz
hearty oyster
#

It does look like the Ex one does provide more data in the structure/level it works with

#

I haven't really looked too far back but it could by either the marshaling problem (I don't think it is or else the strings would probably be junk data or null spaces).

tiny quartz
woeful shadow
#

I was only asking because in the example from the OP, they really only needed the ID of the current active user

hearty oyster
#

My guess is it's a problem with the session isolation and trying to display a message box on another session that isn't yours

tiny quartz
woeful shadow
#

(which is almost always just 1)

hearty oyster
tiny quartz
#

Yeah it is the username I want

woeful shadow
#

I think the WTSSendMessage was wrong because of the SessionId

#

but maybe because of the others being unsigned

hearty oyster
woeful shadow
#

The thing that bothers me, btw. ...

#

All of those ints are DWORD in the method sig

tiny quartz
#

Yes which is confusing me!

woeful shadow
#

But the (working) method signature I found is doing one as I4 and the others as U4

tiny quartz
#

so wrong docs?

woeful shadow
#

I suspect they could all be U4

hearty oyster
#

eh technically a DWORD is unsigned but they all end up being the same thing

#

it's easier to just deal with an int and not worry about the signed bit

woeful shadow
#

and it would only matter if the sessionid was actually too large

hearty oyster
#

evne then it doesn't matter if you get the session id using a int signature because the underlying bytes are the same

tiny quartz
#

so we are back to does the MarshalAs really matter in this case?

hearty oyster
#

it doesn't, well not for the int values

woeful shadow
#

Uh, it does

#

Because without it, it doesn't work ... and we didnt' specify it for the strings

#

:-p

tiny quartz
#

yes. hehe. that is the confusion.

hearty oyster
#

Granted I haven't looked into the original issue but if you get the active session id using an int and not uint then the important bit is you specify the call with an int

tiny quartz
tiny quartz
hearty oyster
#

If the raw value was 0xFFFFFFFF then it's just -1 as an unsigned value and thus translates properly back. Even then I think if you swap and change them it won't matter as much because it's going to be the same raw bytes, the int vs uint side is how C# represents them to the end user

#

The important thing is the size of the integer is right. 99% of the time you are best just keeping it all signed (so int not uint) as that's the default integer type used in C#. Even then I cannot think of that 1% scenario where it really matters too much

tiny quartz
#

Well without that attribute I think the issue was the message was cut off.

woeful shadow
#

shrug

hearty oyster
#

Strings are a bit different, you technically don't need the MarshalAs attribute, it might be nice to be explicit though. I don't know what Jaykul is saying it fixes because if you mix and match Ansi vs Unicode you either get garbled chinese characters or null bytes in the strings

#

Also maybe ignore me, I haven't read the full history of this thread, just saying what I know when it comes to PInvoke calls

woeful shadow
#

Ok, I think I figured it out, and it wasn't the marshalas

#

I started commenting those out and couldn't repro the problem

hearty oyster
#

Surprise 🙂 Having the CharSet.Auto on there automatically adds the MarshalAs attribute to the string arguments. IIRC Auto means LPWStr on Windows.

tiny quartz
tiny quartz
hearty oyster
#

IIRC Auto means LPWStr on Windows.
What I don't know is if Auto changes based on whether the A or W suffix is used

hearty oyster
woeful shadow
#

Basically, what worked (without marshal) is to use WTSSendMessageA and CharSet.Ansi

#

That's what the code I posted above is doing (non-explicitly)

hearty oyster
#

That sounds weird, the W API and Unicode results in it being trimmed?

woeful shadow
#

If I explicitly put

    [DllImport("wtsapi32.dll", EntryPoint = "WTSSendMessageW", SetLastError = true, CharSet = CharSet.Unicode)]
    static extern bool WTSSendMessage(
#

Then I have to use .Length * 2 for the string length parameters

hearty oyster
#

oh yea that makes sense then

#

if the length params are for bytes

woeful shadow
#

Yeah 😄

#

I mean, it's a pain in the neck, but it totally makes sense 🙂

tiny quartz
woeful shadow
#

That's what I originally assumed the problem was

hearty oyster
#

Yea some APIs accept char length vs byte length

tiny quartz
#

I did mention converting to byte array and counting. Would that work too?

hearty oyster
#

you gotta make sure you use the proper one because it matters for WSTR

woeful shadow
#

But then ... the code from Luc "just worked" so I didn't dig any further

hearty oyster
woeful shadow
#

to be clear, it's totally documented

#
[in] pTitle

A pointer to a null-terminated string for the title bar of the message box.

[in] TitleLength

The length, in bytes, of the title bar string.
tiny quartz
tiny quartz
hearty oyster
#

when you provide it as a byte array (string encoded to bytes) then you have the length of the array

tiny quartz
hearty oyster
#

when you are providing it as a string you need to deal with how large that string is when encoded. For a Unicode/Wide String function, that's essentially the .NET char count * 2

tiny quartz
#

I guess I’m just asking is string length times two good enough?

hearty oyster
#

For the Unicode one yea

woeful shadow
#

Encoding.UTF8.GetByteCount( is the right solution, btw, not just doubling it 😉

hearty oyster
#

for the Ansi one no because the strings will be encoded with a single byte encoding

hearty oyster
#

Which in .NET is equal to $string.Length * 2

woeful shadow
#

Yeah, match whatever you put in CharSet 😛

hearty oyster
#

And to ensure you can handle non-ASCII chars you essentially need to use the Unicode/W APIs

woeful shadow
#

yeah, exactly

tiny quartz
#

Would emojis still work in both methods?

hearty oyster
#

Technically the A ones support UTF-8 but it needs the manifest entyr which you can't control in powershell

hearty oyster
woeful shadow
#

Honestly, the ANSI methods will ... almost always not work from PowerShell

#

unless you're using ANSI characters

#

I think

hearty oyster
#

So unless you control the exe you should use the Unicode/W APIs

woeful shadow
#

I basically never use them on purpose

#

No real reason to, when you're in .net anywayu

tiny quartz
#

Right because a string in .net is utf-16 le or the like?

hearty oyster
#

Pretty much

#

It's useful for other applications where you could be storing strings with UTF-8. Very common on code written on other platforms

hearty oyster
#

So now with the UTF-8 locale you no longer need a shim to translate the strings in your application. You can mark your manifest as supporting UTF-8 and call the *A functions without having to re-encode them. But this is all stuff outside of .NET/PowerShell so not super relevant here

#

Less optimisation, just simpler code as Windows internally will still translate the stuff to wide chars internally for you

woeful shadow
hearty oyster
#

The *A functions are basically shims that re-call the equivalent *W ones after they've re-encoded strings

royal vortex
#

Interesting discussion, i'll take a look at it tommorow