SENDINPUT doesn't send keystroke to RDP - c#

Using SENDINPUT to press virtual keys works perfect for my application on any open field. Great that's what it is for :) However. Recently a bug report was made that it doesn't work in a RDP session. So I fire up notepad on my computer scan a barcode (it's how my SENDINPUT is used) and the string gets put up on notepad. I start a RDP session open up notepad and nothing gets sent. Notepad remains blank. So for some strange reason the SENDINPUT is not going through the RDP tunnel. I have a feeling it is more of a setting in the RDP session, but i'm not so naive as to think that my code can't be at fault.The code is nothing special but I'll post the important part (the actual call to SEND INPUT)
public class User32Input
{
public static void DoPressRawKey(byte ascii)
{
var inputs = PressRawKey(ascii);
int size = System.Runtime.InteropServices.Marshal.SizeOf(typeof(INPUT));
SendInput((uint)inputs.Count, inputs.ToArray(), size);
}
internal static System.Collections.Generic.List<INPUT> PressRawKey(byte ascii)
{
var inputs = new System.Collections.Generic.List<INPUT>();
inputs.Add(GetRawKeyDown(ascii));
inputs.Add(GetRawKeyUp(ascii));
return inputs;
}
internal static INPUT GetRawKeyDown(byte key)
{
return GetRawKey(key, KEYEVENTF_KEYDN);
}
internal static INPUT GetRawKeyUp(byte key)
{
return GetRawKey(key, KEYEVENTF_KEYUP);
}
private static INPUT GetRawKey(byte key, uint flag)
{
return new INPUT
{
type = User32Input.INPUT_KEYBOARD,
u = new InputUnion
{
ki = new KEYBDINPUT
{
wVk = key,
wScan = 0,
dwFlags = flag,
dwExtraInfo = User32Input.GetMessageExtraInfo(),
}
}
};
}
[DllImport("user32.dll", SetLastError = true)]
private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
}
Any ideas as to why it's not working in a RDP session?

Set the scancode, RDP or ICA sessions - requires KEYBDINPUT.Scan to be non-zero.
Use MapVirtualKey to get it

Related

How do I add a console into my Windows form application? [duplicate]

To get stuck in straight away, a very basic example:
using System;
using System.Windows.Forms;
class test
{
static void Main()
{
Console.WriteLine("test");
MessageBox.Show("test");
}
}
If I compile this with default options (using csc at command line), as expected, it will compile to a console application. Also, because I imported System.Windows.Forms, it will also show a message box.
Now, if I use the option /target:winexe, which I think is the same as choosing Windows Application from within project options, as expected I will only see the Message Box and no console output.
(In fact, the moment it is launched from command line, I can issue the next command before the application has even completed).
So, my question is - I know that you can have "windows"/forms output from a console application, but is there anyway to show the console from a Windows application?
this one should work.
using System.Runtime.InteropServices;
private void Form1_Load(object sender, EventArgs e)
{
AllocConsole();
}
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool AllocConsole();
Perhaps this is over-simplistic...
Create a Windows Form project...
Then: Project Properties -> Application -> Output Type -> Console Application
Then can have Console and Forms running together, works for me
If you are not worrying about opening a console on-command, you can go into the properties for your project and change it to Console Application
.
This will still show your form as well as popping up a console window. You can't close the console window, but it works as an excellent temporary logger for debugging.
Just remember to turn it back off before you deploy the program.
You can call AttachConsole using pinvoke to get a console window attached to a WinForms project: http://www.csharp411.com/console-output-from-winforms-application/
You may also want to consider Log4net ( http://logging.apache.org/log4net/index.html ) for configuring log output in different configurations.
Create a Windows Forms Application, and change the output type to Console.
It will result in both a console and the form to open.
This worked for me, to pipe the output to a file.
Call the console with
cmd /c "C:\path\to\your\application.exe" > myfile.txt
Add this code to your application.
[DllImport("kernel32.dll")]
static extern bool AttachConsole(UInt32 dwProcessId);
[DllImport("kernel32.dll")]
private static extern bool GetFileInformationByHandle(
SafeFileHandle hFile,
out BY_HANDLE_FILE_INFORMATION lpFileInformation
);
[DllImport("kernel32.dll")]
private static extern SafeFileHandle GetStdHandle(UInt32 nStdHandle);
[DllImport("kernel32.dll")]
private static extern bool SetStdHandle(UInt32 nStdHandle, SafeFileHandle hHandle);
[DllImport("kernel32.dll")]
private static extern bool DuplicateHandle(
IntPtr hSourceProcessHandle,
SafeFileHandle hSourceHandle,
IntPtr hTargetProcessHandle,
out SafeFileHandle lpTargetHandle,
UInt32 dwDesiredAccess,
Boolean bInheritHandle,
UInt32 dwOptions
);
private const UInt32 ATTACH_PARENT_PROCESS = 0xFFFFFFFF;
private const UInt32 STD_OUTPUT_HANDLE = 0xFFFFFFF5;
private const UInt32 STD_ERROR_HANDLE = 0xFFFFFFF4;
private const UInt32 DUPLICATE_SAME_ACCESS = 2;
struct BY_HANDLE_FILE_INFORMATION
{
public UInt32 FileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime;
public UInt32 VolumeSerialNumber;
public UInt32 FileSizeHigh;
public UInt32 FileSizeLow;
public UInt32 NumberOfLinks;
public UInt32 FileIndexHigh;
public UInt32 FileIndexLow;
}
static void InitConsoleHandles()
{
SafeFileHandle hStdOut, hStdErr, hStdOutDup, hStdErrDup;
BY_HANDLE_FILE_INFORMATION bhfi;
hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
hStdErr = GetStdHandle(STD_ERROR_HANDLE);
// Get current process handle
IntPtr hProcess = Process.GetCurrentProcess().Handle;
// Duplicate Stdout handle to save initial value
DuplicateHandle(hProcess, hStdOut, hProcess, out hStdOutDup,
0, true, DUPLICATE_SAME_ACCESS);
// Duplicate Stderr handle to save initial value
DuplicateHandle(hProcess, hStdErr, hProcess, out hStdErrDup,
0, true, DUPLICATE_SAME_ACCESS);
// Attach to console window – this may modify the standard handles
AttachConsole(ATTACH_PARENT_PROCESS);
// Adjust the standard handles
if (GetFileInformationByHandle(GetStdHandle(STD_OUTPUT_HANDLE), out bhfi))
{
SetStdHandle(STD_OUTPUT_HANDLE, hStdOutDup);
}
else
{
SetStdHandle(STD_OUTPUT_HANDLE, hStdOut);
}
if (GetFileInformationByHandle(GetStdHandle(STD_ERROR_HANDLE), out bhfi))
{
SetStdHandle(STD_ERROR_HANDLE, hStdErrDup);
}
else
{
SetStdHandle(STD_ERROR_HANDLE, hStdErr);
}
}
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
// initialize console handles
InitConsoleHandles();
if (args.Length != 0)
{
if (args[0].Equals("waitfordebugger"))
{
MessageBox.Show("Attach the debugger now");
}
if (args[0].Equals("version"))
{
#if DEBUG
String typeOfBuild = "d";
#else
String typeOfBuild = "r";
#endif
String output = typeOfBuild + Assembly.GetExecutingAssembly()
.GetName().Version.ToString();
//Just for the fun of it
Console.Write(output);
Console.Beep(4000, 100);
Console.Beep(2000, 100);
Console.Beep(1000, 100);
Console.Beep(8000, 100);
return;
}
}
}
I found this code here: http://www.csharp411.com/console-output-from-winforms-application/
I thought is was worthy to post it here as well.
There are basically two things that can happen here.
Console output
It is possible for a winforms program to attach itself to the console window that created it (or to a different console window, or indeed to a new console window if desired). Once attached to the console window Console.WriteLine() etc works as expected. One gotcha to this approach is that the program returns control to the console window immediately, and then carries on writing to it, so the user can also type away in the console window. You can use start with the /wait parameter to handle this I think.
Link to start Command syntax
Redirected console output
This is when someone pipes the output from your program somewhere else, eg.
yourapp > file.txt
Attaching to a console window in this case effectively ignores the piping. To make this work you can call Console.OpenStandardOutput() to get a handle to the stream that the output should be piped to. This only works if the output is piped, so if you want to handle both of the scenarios you need to open the standard output and write to it and attach to the console window. This does mean that the output is sent to the console window and to the pipe but its the best solution I could find. Below the code I use to do this.
// This always writes to the parent console window and also to a redirected stdout if there is one.
// It would be better to do the relevant thing (eg write to the redirected file if there is one, otherwise
// write to the console) but it doesn't seem possible.
public class GUIConsoleWriter : IConsoleWriter
{
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool AttachConsole(int dwProcessId);
private const int ATTACH_PARENT_PROCESS = -1;
StreamWriter _stdOutWriter;
// this must be called early in the program
public GUIConsoleWriter()
{
// this needs to happen before attachconsole.
// If the output is not redirected we still get a valid stream but it doesn't appear to write anywhere
// I guess it probably does write somewhere, but nowhere I can find out about
var stdout = Console.OpenStandardOutput();
_stdOutWriter = new StreamWriter(stdout);
_stdOutWriter.AutoFlush = true;
AttachConsole(ATTACH_PARENT_PROCESS);
}
public void WriteLine(string line)
{
_stdOutWriter.WriteLine(line);
Console.WriteLine(line);
}
}
//From your application set the Console to write to your RichTextkBox
//object:
Console.SetOut(new RichTextBoxWriter(yourRichTextBox));
//To ensure that your RichTextBox object is scrolled down when its text is
//changed add this event:
private void yourRichTextBox_TextChanged(object sender, EventArgs e)
{
yourRichTextBox.SelectionStart = yourRichTextBox.Text.Length;
yourRichTextBox.ScrollToCaret();
}
public delegate void StringArgReturningVoidDelegate(string text);
public class RichTextBoxWriter : TextWriter
{
private readonly RichTextBox _richTextBox;
public RichTextBoxWriter(RichTextBox richTexttbox)
{
_richTextBox = richTexttbox;
}
public override void Write(char value)
{
SetText(value.ToString());
}
public override void Write(string value)
{
SetText(value);
}
public override void WriteLine(char value)
{
SetText(value + Environment.NewLine);
}
public override void WriteLine(string value)
{
SetText(value + Environment.NewLine);
}
public override Encoding Encoding => Encoding.ASCII;
//Write to your UI object in thread safe way:
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (_richTextBox.InvokeRequired)
{
var d = new StringArgReturningVoidDelegate(SetText);
_richTextBox.Invoke(d, text);
}
else
{
_richTextBox.Text += text;
}
}
}
Building on Chaz's answer, in .NET 5 there is a breaking change, so two modifications are required in the project file, i.e. changing OutputType and adding DisableWinExeOutputInference. Example:
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0-windows10.0.17763.0</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<DisableWinExeOutputInference>true</DisableWinExeOutputInference>
<Platforms>AnyCPU;x64;x86</Platforms>
</PropertyGroup>
using System;
using System.Runtime.InteropServices;
namespace SomeProject
{
class GuiRedirect
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AttachConsole(int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetStdHandle(StandardHandle nStdHandle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetStdHandle(StandardHandle nStdHandle, IntPtr handle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern FileType GetFileType(IntPtr handle);
private enum StandardHandle : uint
{
Input = unchecked((uint)-10),
Output = unchecked((uint)-11),
Error = unchecked((uint)-12)
}
private enum FileType : uint
{
Unknown = 0x0000,
Disk = 0x0001,
Char = 0x0002,
Pipe = 0x0003
}
private static bool IsRedirected(IntPtr handle)
{
FileType fileType = GetFileType(handle);
return (fileType == FileType.Disk) || (fileType == FileType.Pipe);
}
public static void Redirect()
{
if (IsRedirected(GetStdHandle(StandardHandle.Output)))
{
var initialiseOut = Console.Out;
}
bool errorRedirected = IsRedirected(GetStdHandle(StandardHandle.Error));
if (errorRedirected)
{
var initialiseError = Console.Error;
}
AttachConsole(-1);
if (!errorRedirected)
SetStdHandle(StandardHandle.Error, GetStdHandle(StandardHandle.Output));
}
}
Setting the output type as Console in the project properties will give you a Console application along with the form you created.
if what you want is simple debug output the following works for me. I am using VS 2022 programming in C#
add "using System.Diagnostics"
then
Debug.WriteLine("*****");
Debug.WriteLine(...);
Debug.WriteLine("");
THe output appears in the debug console of VS2022. There is a lot of stuff there so I use the Debug.WriteLine("*****") and Debug.WriteLine("") to help me find my output. You can also clear the debug output after start up.
I am still working but running under VS there is no output when running wo debugging
Why not just leave it as a Window Forms app, and create a simple form to mimic the Console. The form can be made to look just like the black-screened Console, and have it respond directly to key press.
Then, in the program.cs file, you decide whether you need to Run the main form or the ConsoleForm. For example, I use this approach to capture the command line arguments in the program.cs file. I create the ConsoleForm, initially hide it, then pass the command line strings to an AddCommand function in it, which displays the allowed commands. Finally, if the user gave the -h or -? command, I call the .Show on the ConsoleForm and when the user hits any key on it, I shut down the program. If the user doesn't give the -? command, I close the hidden ConsoleForm and Run the main form.
You can any time switch between type of applications, to console or windows. So, you will not write special logic to see the stdout. Also, when running application in debugger, you will see all the stdout in output window. You might also just add a breakpoint, and in breakpoint properties change "When Hit...", you can output any messages, and variables. Also you can check/uncheck "Continue execution", and your breakpoint will become square shaped. So, the breakpoint messages without changhing anything in the application in the debug output window.

Can't figure out how to check if Biometric is present

At work we make our own tablets. Some of the tablets have fingerprint biometrics, some don't. Sometimes a tech forgets to plug it in. I have yet to find a way to check if that device (or any for that matter) is present.
My first approach was to use the GUID for a biometric which is {53D29EF7-377C-4D14-864B-EB3A85769359}. I would search in the registry at hklm\system\currontcontrolset\control\class and check to see if that key is present.
That doesn't work because it seems that Windows 7 has that key present even if you've never had a biometric installed. It worked in XP, but I just tried again on a unit that used to have a biometric but I took it out and that key is still present.
The hardest part about this problem is that I have to work with Windows 7, 7 embedded, xp, and xp embedded.
Next idea was to use WMI, but I couldn't find the correct class to call to check if it is present.
I then found a biometric.dll but that only works in Windows 7.
Sometimes finding a common solution to a problem is not always easy. I'm doing this project in C# but iI'm willing to convert it to any language.
Any ideas on were I should start looking?
With the help of Joshua Drake who gave me an awesome link on how to solve my problem, those are my results:
The code that I am fixing to post is kind of specialized in that it looks for a specific GUID and only looks for the first one. I adapted it from the article about how to disable a device, although this code does not disable anything it merely checks for presence.
public static bool IsDevicePresent(string guid)
{
var info = IntPtr.Zero;
var NullGuid = new Guid(guid);
try
{
info = SetupDiGetClassDevsW(ref NullGuid,null,IntPtr.Zero,DIGCF_PRESENT);
CheckError("SetupDiGetClassDevs");
var devdata = new SP_DEVINFO_DATA();
devdata.cbSize = (UInt32)Marshal.SizeOf(devdata);
// Get first device matching device criterion.
SetupDiEnumDeviceInfo(info,0,out devdata);
// if no items match filter, throw
if (Marshal.GetLastWin32Error() == ERROR_NO_MORE_ITEMS)
CheckError("No device found matching filter.", 0xcffff);
CheckError("SetupDiEnumDeviceInfo");
}
catch
{
return false;
}
finally
{
if (info != IntPtr.Zero)
SetupDiDestroyDeviceInfoList(info);
}
return true;
}
private static void CheckError(string message, int lasterror = -1)
{
int code = lasterror == -1 ? Marshal.GetLastWin32Error() : lasterror;
if (code != 0)
throw new ApplicationException(String.Format("Error disabling hardware device (Code {0}): {1}",code, message));
}
[DllImport("setupapi.dll", SetLastError = true)]
private static extern IntPtr SetupDiGetClassDevsW([In] ref Guid ClassGuid,[MarshalAs(UnmanagedType.LPWStr)]string Enumerator,IntPtr parent,UInt32 flags);
[DllImport("setupapi.dll", SetLastError = true)]
private static extern bool SetupDiDestroyDeviceInfoList(IntPtr handle);
[DllImport("setupapi.dll", SetLastError = true)]
private static extern bool SetupDiEnumDeviceInfo(IntPtr deviceInfoSet,UInt32 memberIndex,[Out] out SP_DEVINFO_DATA deviceInfoData);
//used to find device info from device manager
[StructLayout(LayoutKind.Sequential)]
private struct SP_DEVINFO_DATA
{
public UInt32 cbSize;
public Guid classGuid;
public UInt32 devInst;
public IntPtr reserved;
}
private const uint DIGCF_PRESENT = 2;
private const uint ERROR_INVALID_DATA = 13;
private const uint ERROR_NO_MORE_ITEMS = 259;
private const uint ERROR_ELEMENT_NOT_FOUND = 1168;
And here is a simple unit test to prove it works for first device
[Test]
public void TestDevicePresent()
{
var bluetoothClassGuid = "e0cbf06c-cd8b-4647-bb8a-263b43f0f974";
var biometricClassGuid = "53D29EF7-377C-4D14-864B-EB3A85769359";
var cdromdrivClassGiud = "4d36e965-e325-11ce-bfc1-08002be10318";
Assert.False(Native.IsDevicePresent(bluetoothClassGuid));
Assert.False(Native.IsDevicePresent(biometricClassGuid));
Assert.True(Native.IsDevicePresent(cdromdrivClassGiud));
}

How to get a Unique ID for the current user's logon session in windows - c#

I need to get a value that uniquely identifies the current windows user's logon session. This is for a winforms app, not ASP.NET. I'll be retrieving this from within multiple processes so it needs to return the same value when retrieved within the same logon session. It only needs to be unique on the current machine for the duration of all user sessions - e.g. until the machine is next restarted.
I think the Windows Logon Id is the right thing, but seems a bit of a pain to retrieve. Is there anything else or any easier way to get this?
I'll be using the ID to include in an address of a named pipes service to communicate between two processes running on the machine. I want to include the Logon Id to avoid conflicts when there are multiple users logged in, including perhaps multiple sessions of the same user.
The easiest way to get Session Id is to look at Process.SessionId property:
System.Diagnostics.Process.GetCurrentProcess().SessionId
The value is the same as returned by GetTokenInformation(...,TokenSessionId,...).
Note: One thing you should keep in mind is that Session Id is not a Logon Id.
For example, on Win7 elevated process launched under the same user in the same session will have different LogonId (copmaring to non-elevated one), but will have the same SessionId.
Even runniung non-elevated process with RunAs explicitly specifying credentials of the same user will create new Logon Sesison Id.
Such behavior has meaning, for example, when you map network drives. Since Vista, a process with one token LogonId will not see network dirves mapped with another LogonId, even if processes are running in the same session and under same user.
Below is the sample app that you can launch on different sessions/creds to see the difference:
using System;
using System.Runtime.InteropServices;
namespace GetCurrentSessionId
{
class Program
{
enum TokenInformationClass
{
TokenUser = 1,
TokenGroups,
TokenPrivileges,
TokenOwner,
TokenPrimaryGroup,
TokenDefaultDacl,
TokenSource,
TokenType,
TokenImpersonationLevel,
TokenStatistics,
TokenRestrictedSids,
TokenSessionId,
TokenGroupsAndPrivileges,
TokenSessionReference,
TokenSandBoxInert,
TokenAuditPolicy,
TokenOrigin
}
enum TokenType
{
TokenPrimary = 1,
TokenImpersonation
}
enum SecurityImpersonationLevel
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
[StructLayout(LayoutKind.Sequential)]
struct TokenStatistics
{
public Int64 TokenId;
public Int64 AuthenticationId;
public Int64 ExpirationTime;
public TokenType TokenType;
public SecurityImpersonationLevel ImpersonationLevel;
public Int32 DynamicCharged;
public Int32 DynamicAvailable;
public Int32 GroupCount;
public Int32 PrivilegeCount;
public Int64 ModifiedId;
}
struct TokenOrigin
{
public Int64 OriginatingLogonSession;
}
[DllImport("advapi32.dll", EntryPoint = "GetTokenInformation", SetLastError = true)]
static extern bool GetTokenInformation(
IntPtr tokenHandle,
TokenInformationClass tokenInformationClass,
IntPtr tokenInformation,
int tokenInformationLength,
out int ReturnLength);
public const int ERROR_INSUFFICIENT_BUFFER = 0x7a;
static void Main(string[] args)
{
try
{
Console.WriteLine("Session Id: {0}", System.Diagnostics.Process.GetCurrentProcess().SessionId);
IntPtr tokenInfo;
bool result;
int infoSize;
IntPtr hToken = System.Security.Principal.WindowsIdentity.GetCurrent().Token;
result = GetTokenInformation(hToken, TokenInformationClass.TokenStatistics, IntPtr.Zero, 0, out infoSize);
if (!result && Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER)
{
tokenInfo = Marshal.AllocHGlobal(infoSize);
result = GetTokenInformation(hToken, TokenInformationClass.TokenStatistics, tokenInfo, infoSize, out infoSize);
if (result)
{
TokenStatistics tokenStats = (TokenStatistics)Marshal.PtrToStructure(tokenInfo, typeof(TokenStatistics));
Console.WriteLine("LogonId: 0x{0:X16}", tokenStats.AuthenticationId);
}
else
{
Console.Error.WriteLine("LogonId: FAILED with 0x{0:X08}", Marshal.GetLastWin32Error());
}
Marshal.FreeHGlobal(tokenInfo);
}
else
{
Console.Error.WriteLine("LogonId: FAILED with 0x{0:X08}", Marshal.GetLastWin32Error());
}
tokenInfo = Marshal.AllocHGlobal(sizeof (Int32));
result = GetTokenInformation(hToken, TokenInformationClass.TokenSessionId, tokenInfo, sizeof (Int32), out infoSize);
if (result)
{
int tokenSessionId = Marshal.ReadInt32(tokenInfo);
Console.WriteLine("TokenSessionId: {0}", tokenSessionId);
}
else
{
Console.Error.WriteLine("TokenSessionId: FAILED with 0x{0:X08}", Marshal.GetLastWin32Error());
}
Marshal.FreeHGlobal(tokenInfo);
result = GetTokenInformation(hToken, TokenInformationClass.TokenOrigin, IntPtr.Zero, 0, out infoSize);
if (!result && Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER)
{
tokenInfo = Marshal.AllocHGlobal(infoSize);
result = GetTokenInformation(hToken, TokenInformationClass.TokenOrigin, tokenInfo, infoSize, out infoSize);
if (result)
{
TokenOrigin tokenOrigin = (TokenOrigin) Marshal.PtrToStructure(tokenInfo, typeof (TokenOrigin));
Console.WriteLine("OriginatingSessionId: 0x{0:X16}", tokenOrigin.OriginatingLogonSession);
}
else
{
Console.WriteLine("OriginatingSessionId: FAILED with 0x{0:X08}", Marshal.GetLastWin32Error());
}
Marshal.FreeHGlobal(tokenInfo);
}
else
{
Console.WriteLine("OriginatingSessionId: FAILED with 0x{0:X08}", Marshal.GetLastWin32Error());
}
Console.WriteLine("Press any key...");
Console.ReadKey();
}
catch (Exception ex)
{
Console.Error.WriteLine("Unexpected error: {0}", ex);
Console.ReadKey();
}
}
}
}
As I understand, what you need is this:
SID: S-1-5-5-X-Y
Name: Logon Session
Description: A logon session. The X and Y values for these SIDs are different for each session.
Well-known security identifiers in Windows operating systems
http://support.microsoft.com/kb/243330
Somebody asked for something similar here:
How to get the logon SID in C#
They have a good answer there, but I want to add my own
This is my solution:
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace TestLogonSid
{
public partial class Form1 : Form
{
private delegate bool EnumDesktopProc(string lpszDesktop, IntPtr lParam);
public Form1()
{
InitializeComponent();
}
private void button1_Click_1(object sender, EventArgs e)
{
this.textBox1.Text = GetLogonSid.getLogonSid();
}
}
public class GetLogonSid
{
//The SID structure that identifies the user that is currently associated with the specified object.
//If no user is associated with the object, the value returned in the buffer pointed to by lpnLengthNeeded is zero.
//Note that SID is a variable length structure.
//You will usually make a call to GetUserObjectInformation to determine the length of the SID before retrieving its value.
private const int UOI_USER_SID = 4;
//GetUserObjectInformation function
//Retrieves information about the specified window station or desktop object.
[DllImport("user32.dll")]
static extern bool GetUserObjectInformation(IntPtr hObj, int nIndex, [MarshalAs(UnmanagedType.LPArray)] byte[] pvInfo, int nLength, out uint lpnLengthNeeded);
//GetThreadDesktop function
//Retrieves a handle to the desktop assigned to the specified thread.
[DllImport("user32.dll")]
private static extern IntPtr GetThreadDesktop(int dwThreadId);
//GetCurrentThreadId function
//Retrieves the thread identifier of the calling thread.
[DllImport("kernel32.dll")]
public static extern int GetCurrentThreadId();
//ConvertSidToStringSid function
//The ConvertSidToStringSid function converts a security identifier (SID) to a string format suitable for display, storage, or transmission.
//To convert the string-format SID back to a valid, functional SID, call the ConvertStringSidToSid function.
[DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool ConvertSidToStringSid(
[MarshalAs(UnmanagedType.LPArray)] byte[] pSID,
out IntPtr ptrSid);
/// <summary>
/// The getLogonSid function returns the Logon Session string
/// </summary>
/// <returns></returns>
public static string getLogonSid()
{
string sidString = "";
IntPtr hdesk = GetThreadDesktop(GetCurrentThreadId());
byte[] buf = new byte[100];
uint lengthNeeded;
GetUserObjectInformation(hdesk, UOI_USER_SID, buf, 100, out lengthNeeded);
IntPtr ptrSid;
if (!ConvertSidToStringSid(buf, out ptrSid))
throw new System.ComponentModel.Win32Exception();
try
{
sidString = Marshal.PtrToStringAuto(ptrSid);
}
catch
{
}
return sidString;
}
}
}
You can try Environment.UserDomainName & Environment.UserName
To ensure a unique id for each user session I guess you'll have to use the painful method you mentioned - How to get the logon SID in C#.
If I understand you correctly you can generate a GUID

Capture screen on server desktop session

I have developed a GUI test framework that does integrationtesting of our company website on a scheduled basis. When something fails, it'll take a screenshot of the desktop, among other things. This runs unattended on a logged in user on a dedicated Windows Server 2008.
The problem is that taking a screenshot on a desktop that I have disconnected my remote desktop session from. I get the following exception:
System.ComponentModel.Win32Exception (0x80004005): The handle is invalid
at System.Drawing.Graphics.CopyFromScreen(Int32 sourceX, Int32 sourceY, Int32 destinationX, Int32 destinationY, Size blockRegionSize, CopyPixelOperation copyPixelOperation)
at System.Drawing.Graphics.CopyFromScreen(Point upperLeftSource, Point upperLeftDestination, Size blockRegionSize)
at IntegrationTester.TestCaseRunner.TakeScreenshot(String name) in C:\VS2010\IntegrationTester\IntegrationTester\Config\TestCaseRunner.cs:line 144
at IntegrationTester.TestCaseRunner.StartTest() in C:\VS2010\IntegrationTester\IntegrationTester\Config\TestCaseRunner.cs:line 96
The TakeScreenshot() method looks like this:
public static void TakeScreenshot(string name)
{
var bounds = Screen.GetBounds(Point.Empty);
using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
{
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);
}
bitmap.Save("someFileName", ImageFormat.Jpeg);
}
}
I have made sure that screensaver is set to "None" with no timeout.
I have also implemented a piece of code that does a couple of pinvokes to send a mouse move, hoping it would generate a desktop graphics handle.. but no.
IntPtr hWnd = GetForegroundWindow();
if (hWnd != IntPtr.Zero)
SendMessage(hWnd, 0x200, IntPtr.Zero, IntPtr.Zero);
Any advice is appreciated.
In order to capture the screen you need to run a program in the session of an user. That is because without the user there is no way to have a desktop associated.
To solve this you can run a desktop application to take the image, this application can be invoked in the session of the active user, this can be done from a service.
The code below allows you to invoke an desktop application in such way that it run on the local user's desktop.
If you need to execute as a particular user, check the code in article Allow service to interact with desktop? Ouch.. You can also consider using the function LogonUser.
The code:
public void Execute()
{
IntPtr sessionTokenHandle = IntPtr.Zero;
try
{
sessionTokenHandle = SessionFinder.GetLocalInteractiveSession();
if (sessionTokenHandle != IntPtr.Zero)
{
ProcessLauncher.StartProcessAsUser("Executable Path", "Command Line", "Working Directory", sessionTokenHandle);
}
}
catch
{
//What are we gonna do?
}
finally
{
if (sessionTokenHandle != IntPtr.Zero)
{
NativeMethods.CloseHandle(sessionTokenHandle);
}
}
}
internal static class SessionFinder
{
private const int INT_ConsoleSession = -1;
internal static IntPtr GetLocalInteractiveSession()
{
IntPtr tokenHandle = IntPtr.Zero;
int sessionID = NativeMethods.WTSGetActiveConsoleSessionId();
if (sessionID != INT_ConsoleSession)
{
if (!NativeMethods.WTSQueryUserToken(sessionID, out tokenHandle))
{
throw new System.ComponentModel.Win32Exception();
}
}
return tokenHandle;
}
}
internal static class ProcessLauncher
{
internal static void StartProcessAsUser(string executablePath, string commandline, string workingDirectory, IntPtr sessionTokenHandle)
{
var processInformation = new NativeMethods.PROCESS_INFORMATION();
try
{
var startupInformation = new NativeMethods.STARTUPINFO();
startupInformation.length = Marshal.SizeOf(startupInformation);
startupInformation.desktop = string.Empty;
bool result = NativeMethods.CreateProcessAsUser
(
sessionTokenHandle,
executablePath,
commandline,
IntPtr.Zero,
IntPtr.Zero,
false,
0,
IntPtr.Zero,
workingDirectory,
ref startupInformation,
ref processInformation
);
if (!result)
{
int error = Marshal.GetLastWin32Error();
string message = string.Format("CreateProcessAsUser Error: {0}", error);
throw new ApplicationException(message);
}
}
finally
{
if (processInformation.processHandle != IntPtr.Zero)
{
NativeMethods.CloseHandle(processInformation.processHandle);
}
if (processInformation.threadHandle != IntPtr.Zero)
{
NativeMethods.CloseHandle(processInformation.threadHandle);
}
if (sessionTokenHandle != IntPtr.Zero)
{
NativeMethods.CloseHandle(sessionTokenHandle);
}
}
}
}
internal static class NativeMethods
{
[DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
internal static extern bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool CreateProcessAsUser(IntPtr tokenHandle, string applicationName, string commandLine, IntPtr processAttributes, IntPtr threadAttributes, bool inheritHandle, int creationFlags, IntPtr envrionment, string currentDirectory, ref STARTUPINFO startupInfo, ref PROCESS_INFORMATION processInformation);
[DllImport("Kernel32.dll", EntryPoint = "WTSGetActiveConsoleSessionId")]
internal static extern int WTSGetActiveConsoleSessionId();
[DllImport("WtsApi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool WTSQueryUserToken(int SessionId, out IntPtr phToken);
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr processHandle;
public IntPtr threadHandle;
public int processID;
public int threadID;
}
[StructLayout(LayoutKind.Sequential)]
internal struct STARTUPINFO
{
public int length;
public string reserved;
public string desktop;
public string title;
public int x;
public int y;
public int width;
public int height;
public int consoleColumns;
public int consoleRows;
public int consoleFillAttribute;
public int flags;
public short showWindow;
public short reserverd2;
public IntPtr reserved3;
public IntPtr stdInputHandle;
public IntPtr stdOutputHandle;
public IntPtr stdErrorHandle;
}
}
This code is a modification of the one found at the article Allow service to interact with desktop? Ouch. (MUST READ)
Addendum:
The code above allows to execute a program in the desktop of the user logged locally on the machine. This method specific for the current local user, but it is possible to do it for any user. Check the code at the article Allow service to interact with desktop? Ouch. for an example.
The core of this method is the function CreateProcessAsUser, you can find more about at MSDN.
Replace "Executable Path" with the path of the executable to run. Replace "Command Line" with the string passed as execution arguments, and replace "Working Directory" with the working directory you want. For example you can extract the folder of the executable path:
internal static string GetFolder(string path)
{
var folder = System.IO.Directory.GetParent(path).FullName;
if (!folder.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString()))
{
folder += System.IO.Path.DirectorySeparatorChar;
}
return folder;
}
If you have a service, you can use this code in the service to invoke a desktop application. That desktop application may also be the the service executable... for that you can use Assembly.GetExecutingAssembly().Location as the executable path. Then you can use System.Environment.UserInteractive to detect if the executable is not running as a service and pass as execution arguments information about the task needed to do. In the context of this answer that is to capture the screen (for example with CopyFromScreen), it could be something else.
What I did to solve this is call tscon.exe and tell it to redirect the session back to the console just before the screenshot is taken. It goes like this (note, this exact code is untested):
public static void TakeScreenshot(string path) {
try {
InternalTakeScreenshot(path);
} catch(Win32Exception) {
var winDir = System.Environment.GetEnvironmentVariable("WINDIR");
Process.Start(
Path.Combine(winDir, "system32", "tscon.exe"),
String.Format("{0} /dest:console", GetTerminalServicesSessionId()))
.WaitForExit();
InternalTakeScreenshot(path);
}
}
static void InternalTakeScreenshot(string path) {
var point = new System.Drawing.Point(0,0);
var bounds = System.Windows.Forms.Screen.GetBounds(point);
var size = new System.Drawing.Size(bounds.Width, bounds.Height);
var screenshot = new System.Drawing.Bitmap(bounds.Width, bounds.Height);
var g = System.Drawing.Graphics.FromImage(screenshot)
g.CopyFromScreen(0,0,0,0,size);
screenshot.Save(path, System.Drawing.Imaging.ImageFormat.Jpeg);
}
[DllImport("kernel32.dll")]
static extern bool ProcessIdToSessionId(uint dwProcessId, out uint pSessionId);
static uint GetTerminalServicesSessionId()
{
var proc = Process.GetCurrentProcess();
var pid = proc.Id;
var sessionId = 0U;
if(ProcessIdToSessionId((uint)pid, out sessionId))
return sessionId;
return 1U; // fallback, the console session is session 1
}
This is not a supported feature it is true that it works in XP and windows server 2003 however this is seen as security flaw.
To prevent this, don't use the 'x' to close the remote connection, but use %windir%\system32\tscon.exe 0 /dest:console instead. (That will insure that the screen isn't locked). - Nicolas Voron
It true that if you disconnect from the server in this way the "screen" wont be locked to ensure it stay unlocked you need to make sure you turn off the screen saver since as soon as that start up it will auto lock you screen.
There is quite a few examples only of people doing the same thing even here at stack overflow the post below suggest that you create a windows application that run under an actual user account that sends screen shots over IPC to the running service.
The correct way to get a custom GUI that works with a service is to
separate them into two processes and do some kind of IPC (inter
process communication). So the service will start-up when the machine
comes up and a GUI application will be started in the user session. In
that case the GUI can create a screenshot, send it to the service and
the service can do with it, whatever you like. - Screenshot of process under Windows Service
I have collated a few strategies I have found online that may give you some ideas.
Third party software
There is a lot of programs out there that make screen shots of web sites like http://www.websitescreenshots.com/ they have a user interface and command line tool. But if you are using some testing framework this might not work since it will make a new request to fetch all the assets and draw the page.
WebBrowser control
I am not sure what browser you are using to test you company web site however if you are not bothered about which browser It is you could use a WebBrowser control and use the DrawToBitmap method.
Virtualisation
I have seen a system where the developers were using virtual environments with the browser of their choice with all the settings done to make sure the machine didn't lock and if it did it would restart.
Selenium
It is also possible using selenium with the selenium-webdriver and headless a ruby gem developed by leonid-shevtsov if your test are in selenium this approach might be the best. Selenium itself support screen capture on the webdrivers they have available.
Of course all of this depends on what you are using for your testing framework if you can share some details on your setup we will be able to give you a better answer.
The problem seems to be that when you close the remote connection, the screen goes in a locked state which prevent the system to perform graphics operation like your g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);
To prevent this, don't use the 'x' to close the remote connection, but use %windir%\system32\tscon.exe 0 /dest:console instead. (That will insure that the screen isn't locked).
Read this post for further informations (in VBA, but c#-understandable ;-) )
EDIT
If you want to do it directly in c#, try something like this :
Process p = new Process();
p.StartInfo.FileName = "tscon";
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
p.StartInfo.Arguments = "0 /dest:console";
p.Start();
I found a similar question Screen capture with C# and Remote Desktop problems. Hope it helps you solve the problem.
Here is the code from that answer:
public Image CaptureWindow(IntPtr handle)
{
// get te hDC of the target window
IntPtr hdcSrc = User32.GetWindowDC(handle);
// get the size
User32.RECT windowRect = new User32.RECT();
User32.GetWindowRect(handle, ref windowRect);
int width = windowRect.right - windowRect.left;
int height = windowRect.bottom - windowRect.top;
// create a device context we can copy to
IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc);
// create a bitmap we can copy it to,
// using GetDeviceCaps to get the width/height
IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc, width, height);
// select the bitmap object
IntPtr hOld = GDI32.SelectObject(hdcDest, hBitmap);
// bitblt over
GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, GDI32.SRCCOPY);
// restore selection
GDI32.SelectObject(hdcDest, hOld);
// clean up
GDI32.DeleteDC(hdcDest);
User32.ReleaseDC(handle, hdcSrc);
// get a .NET image object for it
Image img = Image.FromHbitmap(hBitmap);
// free up the Bitmap object
GDI32.DeleteObject(hBitmap);
return img;
}
I think the problem may be that you're on the wrong WindowStation. Have a look at these articles;
Why does print screen in a Windows Service return a black image?
Screen capture from windows service
It could be that your win-station is dissappearing when you disconnect. Are you running the application when you log in and then trying to leave it running when you disconnect?
If so, does it still do it if you connect with "mstsc /admin"? In other words, connecting to and running on the console session? If not, this might be a workaround.

How to determine the current windows user from a windows service?

I'm writing a Windows Service using C# .NET 2005. How can I determine who the currently logged-on user is (if any)? Also is there a way to be notified when a user logs on?
Alternatively, is there a way to know who has recently used the machine?
I need to know the currently logged on user so I can cache some data for that user. Operating in a corporate environment there are thousands of potential users but it only makes sense to cache data for someone who uses that machine.
UPDATE:
This solution works well. Also see this pinvoke.net example which uses the extended structure to also retrieve domain name.
In combination with this I'm using the SystemEvents class to be notified when a user logs on to the machine. See example 2 here for a good example - note that you need to use a hidden form from a service in order to be able to use SystemEvents from a service.
You can use P/Invoke to call NetWkstaUserEnum, which will enumerate the currently logged on users. Keep in mind that there might be more than one user in case there are terminal server sessions, and that not all users returned is a "real" user. As the documentation states:
"This list includes interactive,
service and batch logons."
Here is a complete working code example in C# on how to call NetWkstaUserEnum:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
namespace EnumerateUsers
{
class Program
{
static void Main(string[] args)
{
var ue = new UserEnumerator();
foreach(string userName in ue.GetLoggedOnUsers(null))
{
Console.WriteLine(userName);
}
}
}
class UserEnumerator
{
public IEnumerable<string> GetLoggedOnUsers(string host)
{
int entriesRead, totalEntries, resumeHandle = 0;
IntPtr pBuffer = IntPtr.Zero;
try
{
int result = NetWkstaUserEnum(host, 0, out pBuffer, MAX_PREFERRED_LENGTH, out entriesRead, out totalEntries, ref resumeHandle);
if (result != NERR_Success)
throw new ApplicationException(String.Format("Failed to enumerate users, error code {0}", result));
return GetUsersFromStruct(pBuffer, entriesRead).ToList();
}
finally
{
if (pBuffer != IntPtr.Zero)
NetApiBufferFree(pBuffer);
}
}
private IEnumerable<string> GetUsersFromStruct(IntPtr pBuffer, int count)
{
for (int i = 0; i < count; i++)
{
var user = (WKSTA_USER_INFO_0)Marshal.PtrToStructure(pBuffer, typeof(WKSTA_USER_INFO_0));
yield return user.username;
pBuffer = IntPtr.Add(pBuffer, user.username.Length * 2);
}
}
[DllImport("netapi32.dll")]
private static extern int NetWkstaUserEnum(string host, int level, out IntPtr pBuffer, int prefMaxLength, out int entriesRead,
out int totalEntries, ref int resumeHandle);
[DllImport("netapi32.dll")]
private static extern int NetApiBufferFree(IntPtr buffer);
private const int MAX_PREFERRED_LENGTH = -1;
private const int NERR_Success = 0;
}
[StructLayout(LayoutKind.Sequential)]
struct WKSTA_USER_INFO_0
{
[MarshalAs(UnmanagedType.LPTStr)]
internal string username;
}
}
As you know, there may not be a currently logged on user within a Windows service. Why not not add a small utility program to the startup logon process on the machine, that will run whenever someone logs on, that will execute a method that calls the data caching functionality in the service.. That way that utility will have access to the logged on users' windows principle Identity.

Categories

Resources