P/Invoke CreateWindowStation and protected memory

B

Bill V.

I'm actually trying to teach myself Powershell, but I have some C# code that
gets compiled at runtime and accessed from within the powershell script. My
ultimate purpose for this is to create some remote install software for
programs that don't like to be automated (I work at a community college and
there are several education applications that aren't made with the command
line in mind - some installs you simply have to hit buttons in the gui).
I've gotten around this by embedding some C# code that gets compiled at the
time of running the script and allows my script access to API function calls
such as FindWindow and Postmessage. The scripts (well, the controling script
which then runs individuals)are then run as a sheduled task so software is
installed and updates are applied if they're needed. Also, they're run as a
specific user who has administrative access to the computer, and access to
the shares where the software or other scripts need to be run (and the system
account wouldn't allow me easy access to shares).

Mostly, things have worked fine, but I've come across a few programs that
just didn't want to work as a scheduled task - but the scripts will work fine
if you call them manually. In digging around, I think I have a solution, but
I'm having trouble implementing it. I believe that the issue is a matter of
the window station that I'm running the software in. I think that my running
script is ending up being isolated from the application itself - so I can't
actually send messages to the application. What I think would be a good
solution is to create a new window station for my script and have both the
script and application run in that windowstation. They should be able to
communicate between each other fine this way, and I still have them seperated
from someone directly on the computer (or it will also work if no one is
signed on at all). Also, from the reading I have done, even if this isn't
the actual solution to the problem that I'm having, it looks like I want to
be able to set this up anyway. Currently, I'm running these scripts on
Windows XP, but it looks like I may have problems running this on Vista.
However, using a seperate window station should work well on Vista even if
there are ways around doing this in XP.

Unfortunately, I don't have any real experiance in C# (I really only have
some C going back a couple years ago), so it hasn't been easy to get things
going. So, hopefully this is actually a simple fix, and I just don't have
the experiance to know where to look.

This is the actual C# code that I'm using (it's modified from a version that
I found on the net - but it's worked well with all of the messaging functions
so it's got to be fairly decent ;) ).

using System;
using System.Runtime.InteropServices;

namespace Win32APIStuff
{
unsafe public class CShWinAPI
{

public enum WINDOWS_STATION_ACCESS_MASK : uint
{
WINSTA_NONE = 0,
WINSTA_ENUMDESKTOPS = 0x0001,
WINSTA_READATTRIBUTES = 0x0002,
WINSTA_ACCESSCLIPBOARD = 0x0004,
WINSTA_CREATEDESKTOP = 0x0008,
WINSTA_WRITEATTRIBUTES = 0x0010,
WINSTA_ACCESSGLOBALATOMS = 0x0020,
WINSTA_EXITWINDOWS = 0x0040,
WINSTA_ENUMERATE = 0x0100,
WINSTA_READSCREEN = 0x0200,
WINSTA_ALL_ACCESS = ( WINSTA_ENUMDESKTOPS |
WINSTA_READATTRIBUTES | WINSTA_ACCESSCLIPBOARD | WINSTA_CREATEDESKTOP |
WINSTA_WRITEATTRIBUTES | WINSTA_ACCESSGLOBALATOMS | WINSTA_EXITWINDOWS |
WINSTA_ENUMERATE | WINSTA_READSCREEN),
}


[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}


public enum DESKTOP_ACCESS_MASK : uint
{
DESKTOP_NONE = 0,
DESKTOP_READOBJECTS = 0x0001,
DESKTOP_CREATEWINDOW = 0x0002,
DESKTOP_CREATEMENU = 0x0004,
DESKTOP_HOOKCONTROL = 0x0008,
DESKTOP_JOURNALRECORD = 0x0010,
DESKTOP_JOURNALPLAYBACK = 0x0020,
DESKTOP_ENUMERATE = 0x0040,
DESKTOP_WRITEOBJECTS = 0x0080,
DESKTOP_SWITCHDESKTOP = 0x0100,
GENERIC_ALL = ( DESKTOP_READOBJECTS | DESKTOP_CREATEWINDOW |
DESKTOP_CREATEMENU | DESKTOP_HOOKCONTROL | DESKTOP_JOURNALRECORD |
DESKTOP_JOURNALPLAYBACK | DESKTOP_ENUMERATE | DESKTOP_WRITEOBJECTS |
DESKTOP_SWITCHDESKTOP),
}



[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}


[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string
lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr
hWndChild, string lpClassName, string lpWindowName);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr
wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam,
IntPtr lParam);

[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr
CreateWindowStation([MarshalAs(UnmanagedType.LPWStr)]string name,
[MarshalAs(UnmanagedType.U4)] int reserved, [MarshalAs(UnmanagedType.U4)]
uint desiredAccess, [MarshalAs(UnmanagedType.U4)] uint attributes);
[DllImport("user32.dll", SetLastError = true)]
static extern bool SetProcessWindowStation(IntPtr hWinSta);
[DllImport("user32.dll", SetLastError = true)]
static extern bool CreateDesktop(string desktopName, string device,
string deviceMode, int flags, uint accessMask, uint attributes);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr OpenWindowStation(String lpszWinSta, bool
fInherit, uint dwDesiredAccess);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr OpenInputDesktop(uint dwFlags, bool fInherit,
uint dwDesiredAccess);
[DllImport("user32.dll", SetLastError = true)]
static extern bool SetThreadDesktop(IntPtr hDesktop);
[DllImport("user32.dll", SetLastError = true)]
static extern bool SwitchDesktop(IntPtr hDesktop);


public static IntPtr RunFindWindow(string ClassNm, string WindowNm)
{
IntPtr FindWindowVar;
FindWindowVar = FindWindow(ClassNm, WindowNm);
return FindWindowVar;
}

public static IntPtr RunFindWindowEx(IntPtr hWdParent, string
ClassNm, string WindowNm)
{
IntPtr FindWindowVar;
FindWindowVar = FindWindowEx(hWdParent, System.IntPtr.Zero,
ClassNm, WindowNm);
return FindWindowVar;
}

public static IntPtr RunSendMessage(IntPtr Window, uint Message,
IntPtr wPar, IntPtr lPar)
{
IntPtr Sent;
Sent = SendMessage(Window, Message, wPar, lPar);
return Sent;
}

public static bool RunPostMessage(IntPtr Window, uint Message,
IntPtr wPar, IntPtr lPar)
{
bool Sent;
Sent = PostMessage(Window, Message, wPar, lPar);
return Sent;
}



public static IntPtr RunCreateWindowStation(string name, uint
desiredAccess, uint attributes)
{
IntPtr Sent;
Sent = CreateWindowStation(name, 0, desiredAccess, attributes);
return Sent;
}

public static bool RunSetProcessWindowStation()
{
bool Sent;
Sent = SetProcessWindowStation(CreateWindowStation("WinStation",
0, 0x037f, 0x01ff));
return Sent;
}

public static bool RunCreateDesktop(string desktopName, string
device, string deviceMode, int flags, uint accessMask, uint attributes)
{
bool Sent;
Sent = CreateDesktop(desktopName, device, deviceMode, flags,
accessMask, attributes);
return Sent;
}

public static IntPtr RunOpenWindowStation(String lpszWinSta, bool
fInherit, uint dwDesiredAccess)
{
IntPtr Sent;
Sent = OpenWindowStation(lpszWinSta, fInherit, dwDesiredAccess);
return Sent;
}

public static IntPtr RunOpenInputDesktop(uint dwFlags, bool
fInherit, uint dwDesiredAccess)
{
IntPtr Sent;
Sent = OpenInputDesktop(dwFlags, fInherit, dwDesiredAccess);
return Sent;
}

public static bool RunSetThreadDesktop(IntPtr hDesktop)
{
bool Sent;
Sent = SetThreadDesktop(hDesktop);
return Sent;
}

public static bool RunSwitchDesktop(IntPtr hDesktop)
{
bool Sent;
Sent = SwitchDesktop(hDesktop);
return Sent;
}


public static IntPtr MakeParam(int xCoordinate, int yCoordinate)
{
return (IntPtr) (((short)yCoordinate << 16) | (xCoordinate &
0xffff));
}


}


}



And, this is the compiler options I'm using

$cpar = New-Object System.CodeDom.Compiler.CompilerParameters
$cpar.GenerateInMemory = $true
$cpar.GenerateExecutable = $false
$cpar.OutputAssembly = "custom"
$cpar.compilerOptions = "/unsafe"
$cpar.ReferencedAssemblies.AddRange($refs)
$cr = $cp.CompileAssemblyFromSource($cpar, $code)


(If you're wondering about the dollar signs, that's how Powershell declairs
its variables).


And, for the sake of completeness, this is the actual line where I call
CreateWinStation (this too is powershell)

[Win32APIStuff.CShWinAPI]::RunCreateWindowStation("WinStation", 0x037f,
0x01ff)


This is the error message that I'm generating


Exception calling "RunCreateWindowStation" with "3" argument(s): "Attempted to
read or write protected memory. This is often an indication that other
memory is corrupt."


However, I think that this is a C# error rather than it being a Powershell
problem. Specifically, if I modify RunCreateWindowStation to take no
arguments and manually put the arguments for CreateWindowStation directly
inside my own C# function, I still get the same problem. So, I don't think
it's a problem with passing values from Powershell to C# - I think the issue
lies with going from C# to the Win32API. From what I've figured out, I
believe that the main issue is the first value - the string (currently
"WinStation"). It seems to me, that the issue is that C# expects the string
to be unchanging, while the API likely wants the ability to make changes
(even though it doesn't need to actually change anything - possibly because
it wants pointer access to the string). But that's just a guess, and not
necessarily a good one. If this is the case, I understand that StringBuilder
would be a better match than a string, but I can't seem to simply switch a
StringBuilder for a string and have it work here (I believe that the error
was in the order of not having an overloaded version of CreateWindowStation
that matched).

So, could anyone help me out here?

Thanks,

Bill V.
 
W

Willy Denoyette [MVP]

Bill V. said:
I'm actually trying to teach myself Powershell, but I have some C# code
that
gets compiled at runtime and accessed from within the powershell script.
My
ultimate purpose for this is to create some remote install software for
programs that don't like to be automated (I work at a community college
and
there are several education applications that aren't made with the command
line in mind - some installs you simply have to hit buttons in the gui).
I've gotten around this by embedding some C# code that gets compiled at
the
time of running the script and allows my script access to API function
calls
such as FindWindow and Postmessage. The scripts (well, the controling
script
which then runs individuals)are then run as a sheduled task so software is
installed and updates are applied if they're needed. Also, they're run as
a
specific user who has administrative access to the computer, and access to
the shares where the software or other scripts need to be run (and the
system
account wouldn't allow me easy access to shares).

Mostly, things have worked fine, but I've come across a few programs that
just didn't want to work as a scheduled task - but the scripts will work
fine
if you call them manually. In digging around, I think I have a solution,
but
I'm having trouble implementing it. I believe that the issue is a matter
of
the window station that I'm running the software in. I think that my
running
script is ending up being isolated from the application itself - so I
can't
actually send messages to the application. What I think would be a good
solution is to create a new window station for my script and have both the
script and application run in that windowstation. They should be able to
communicate between each other fine this way, and I still have them
seperated
from someone directly on the computer (or it will also work if no one is
signed on at all). Also, from the reading I have done, even if this isn't
the actual solution to the problem that I'm having, it looks like I want
to
be able to set this up anyway. Currently, I'm running these scripts on
Windows XP, but it looks like I may have problems running this on Vista.
However, using a seperate window station should work well on Vista even if
there are ways around doing this in XP.

Unfortunately, I don't have any real experiance in C# (I really only have
some C going back a couple years ago), so it hasn't been easy to get
things
going. So, hopefully this is actually a simple fix, and I just don't have
the experiance to know where to look.

This is the actual C# code that I'm using (it's modified from a version
that
I found on the net - but it's worked well with all of the messaging
functions
so it's got to be fairly decent ;) ).

using System;
using System.Runtime.InteropServices;

namespace Win32APIStuff
{
unsafe public class CShWinAPI
{

public enum WINDOWS_STATION_ACCESS_MASK : uint
{
WINSTA_NONE = 0,
WINSTA_ENUMDESKTOPS = 0x0001,
WINSTA_READATTRIBUTES = 0x0002,
WINSTA_ACCESSCLIPBOARD = 0x0004,
WINSTA_CREATEDESKTOP = 0x0008,
WINSTA_WRITEATTRIBUTES = 0x0010,
WINSTA_ACCESSGLOBALATOMS = 0x0020,
WINSTA_EXITWINDOWS = 0x0040,
WINSTA_ENUMERATE = 0x0100,
WINSTA_READSCREEN = 0x0200,
WINSTA_ALL_ACCESS = ( WINSTA_ENUMDESKTOPS |
WINSTA_READATTRIBUTES | WINSTA_ACCESSCLIPBOARD | WINSTA_CREATEDESKTOP |
WINSTA_WRITEATTRIBUTES | WINSTA_ACCESSGLOBALATOMS | WINSTA_EXITWINDOWS |
WINSTA_ENUMERATE | WINSTA_READSCREEN),
}


[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}


public enum DESKTOP_ACCESS_MASK : uint
{
DESKTOP_NONE = 0,
DESKTOP_READOBJECTS = 0x0001,
DESKTOP_CREATEWINDOW = 0x0002,
DESKTOP_CREATEMENU = 0x0004,
DESKTOP_HOOKCONTROL = 0x0008,
DESKTOP_JOURNALRECORD = 0x0010,
DESKTOP_JOURNALPLAYBACK = 0x0020,
DESKTOP_ENUMERATE = 0x0040,
DESKTOP_WRITEOBJECTS = 0x0080,
DESKTOP_SWITCHDESKTOP = 0x0100,
GENERIC_ALL = ( DESKTOP_READOBJECTS | DESKTOP_CREATEWINDOW
|
DESKTOP_CREATEMENU | DESKTOP_HOOKCONTROL | DESKTOP_JOURNALRECORD |
DESKTOP_JOURNALPLAYBACK | DESKTOP_ENUMERATE | DESKTOP_WRITEOBJECTS |
DESKTOP_SWITCHDESKTOP),
}



[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}


[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string
lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr
hWndChild, string lpClassName, string lpWindowName);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError =
true)]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr
wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr
wParam,
IntPtr lParam);

[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr
CreateWindowStation([MarshalAs(UnmanagedType.LPWStr)]string name,
[MarshalAs(UnmanagedType.U4)] int reserved, [MarshalAs(UnmanagedType.U4)]
uint desiredAccess, [MarshalAs(UnmanagedType.U4)] uint attributes);
[DllImport("user32.dll", SetLastError = true)]
static extern bool SetProcessWindowStation(IntPtr hWinSta);
[DllImport("user32.dll", SetLastError = true)]
static extern bool CreateDesktop(string desktopName, string device,
string deviceMode, int flags, uint accessMask, uint attributes);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr OpenWindowStation(String lpszWinSta, bool
fInherit, uint dwDesiredAccess);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr OpenInputDesktop(uint dwFlags, bool fInherit,
uint dwDesiredAccess);
[DllImport("user32.dll", SetLastError = true)]
static extern bool SetThreadDesktop(IntPtr hDesktop);
[DllImport("user32.dll", SetLastError = true)]
static extern bool SwitchDesktop(IntPtr hDesktop);


public static IntPtr RunFindWindow(string ClassNm, string WindowNm)
{
IntPtr FindWindowVar;
FindWindowVar = FindWindow(ClassNm, WindowNm);
return FindWindowVar;
}

public static IntPtr RunFindWindowEx(IntPtr hWdParent, string
ClassNm, string WindowNm)
{
IntPtr FindWindowVar;
FindWindowVar = FindWindowEx(hWdParent, System.IntPtr.Zero,
ClassNm, WindowNm);
return FindWindowVar;
}

public static IntPtr RunSendMessage(IntPtr Window, uint Message,
IntPtr wPar, IntPtr lPar)
{
IntPtr Sent;
Sent = SendMessage(Window, Message, wPar, lPar);
return Sent;
}

public static bool RunPostMessage(IntPtr Window, uint Message,
IntPtr wPar, IntPtr lPar)
{
bool Sent;
Sent = PostMessage(Window, Message, wPar, lPar);
return Sent;
}



public static IntPtr RunCreateWindowStation(string name, uint
desiredAccess, uint attributes)
{
IntPtr Sent;
Sent = CreateWindowStation(name, 0, desiredAccess, attributes);
return Sent;
}

public static bool RunSetProcessWindowStation()
{
bool Sent;
Sent =
SetProcessWindowStation(CreateWindowStation("WinStation",
0, 0x037f, 0x01ff));
return Sent;
}

public static bool RunCreateDesktop(string desktopName, string
device, string deviceMode, int flags, uint accessMask, uint attributes)
{
bool Sent;
Sent = CreateDesktop(desktopName, device, deviceMode, flags,
accessMask, attributes);
return Sent;
}

public static IntPtr RunOpenWindowStation(String lpszWinSta, bool
fInherit, uint dwDesiredAccess)
{
IntPtr Sent;
Sent = OpenWindowStation(lpszWinSta, fInherit,
dwDesiredAccess);
return Sent;
}

public static IntPtr RunOpenInputDesktop(uint dwFlags, bool
fInherit, uint dwDesiredAccess)
{
IntPtr Sent;
Sent = OpenInputDesktop(dwFlags, fInherit, dwDesiredAccess);
return Sent;
}

public static bool RunSetThreadDesktop(IntPtr hDesktop)
{
bool Sent;
Sent = SetThreadDesktop(hDesktop);
return Sent;
}

public static bool RunSwitchDesktop(IntPtr hDesktop)
{
bool Sent;
Sent = SwitchDesktop(hDesktop);
return Sent;
}


public static IntPtr MakeParam(int xCoordinate, int yCoordinate)
{
return (IntPtr) (((short)yCoordinate << 16) | (xCoordinate &
0xffff));
}


}


}



And, this is the compiler options I'm using

$cpar = New-Object System.CodeDom.Compiler.CompilerParameters
$cpar.GenerateInMemory = $true
$cpar.GenerateExecutable = $false
$cpar.OutputAssembly = "custom"
$cpar.compilerOptions = "/unsafe"
$cpar.ReferencedAssemblies.AddRange($refs)
$cr = $cp.CompileAssemblyFromSource($cpar, $code)


(If you're wondering about the dollar signs, that's how Powershell
declairs
its variables).


And, for the sake of completeness, this is the actual line where I call
CreateWinStation (this too is powershell)

[Win32APIStuff.CShWinAPI]::RunCreateWindowStation("WinStation", 0x037f,
0x01ff)


This is the error message that I'm generating


Exception calling "RunCreateWindowStation" with "3" argument(s):
"Attempted to
read or write protected memory. This is often an indication that other
memory is corrupt."


However, I think that this is a C# error rather than it being a Powershell
problem. Specifically, if I modify RunCreateWindowStation to take no
arguments and manually put the arguments for CreateWindowStation directly
inside my own C# function, I still get the same problem. So, I don't
think
it's a problem with passing values from Powershell to C# - I think the
issue
lies with going from C# to the Win32API. From what I've figured out, I
believe that the main issue is the first value - the string (currently
"WinStation"). It seems to me, that the issue is that C# expects the
string
to be unchanging, while the API likely wants the ability to make changes
(even though it doesn't need to actually change anything - possibly
because
it wants pointer access to the string). But that's just a guess, and not
necessarily a good one. If this is the case, I understand that
StringBuilder
would be a better match than a string, but I can't seem to simply switch a
StringBuilder for a string and have it work here (I believe that the error
was in the order of not having an overloaded version of
CreateWindowStation
that matched).

So, could anyone help me out here?

Thanks,

Bill V.


the 4th argument of your CreateWindowStation (attributes) is declared as an
uint, it should be a pointer (ref uint attributes).
Also, you should be aware that calling this API with a non-null Windows
station name requires Administrative privileges, do you really run your PS
scripts as an Administrator?

Willy.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top