Related
I am using Hybridizer for the first time. I am using Windows 10, Visual Studio 2019, CUDA 10.1 and Hybridizer 1.3.0 "Released Sep. 5th 2019". Although I have followed their steps, I keep getting the same error:
Dll load error when loading Hello_World_CUDA.dll: 126
whenever I try to make any simple code to test it like this:
using System;
using Hybridizer.Runtime.CUDAImports;
public class Hello_World
{
[EntryPoint]
public static void Hello()
{
Console.Out.Write("Hello from GPU");
}
static void Main()
{
cuda.DeviceSynchronize();
HybRunner runner = HybRunner.Cuda().SetDistrib(1, 2);
runner.Wrap(new Hello_World()).Hello();
}
}
even when using their examples without any change.
How can I solve this problem?
To troubleshoot please follow the three following steps:
Check file exists and can be loaded from current directory
Make sure dll can be loaded using LoadLibrary
Verify symbol can be found using GetProcAddress
To facilitate your investigation, please find below code to run these assertions
using System;
using System.IO;
using System.Runtime.InteropServices;
using Hybridizer.Runtime.CUDAImports;
public class Hello_World
{
[EntryPoint]
public static void Hello()
{
Console.Out.Write("Hello from GPU");
}
[DllImport("kernel32.dll", EntryPoint = "LoadLibraryA", SetLastError = true)]
static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string name);
[DllImport("kernel32.dll", EntryPoint = "FormatMessage", SetLastError = true, CharSet = CharSet.Unicode)]
static extern int FormatMessage(int dwFlags, IntPtr lpSource, int dwMessageId, int dwLanguageId, [MarshalAs(UnmanagedType.LPArray)] char[] data, uint dwSize, IntPtr args);
[DllImport("kernel32.dll", EntryPoint = "GetProcAddress", SetLastError = true, CharSet = CharSet.Ansi)]
static extern IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string symbol);
static unsafe string ErrorToString(int er)
{
char[] buffer = new char[2048];
int res = FormatMessage(0x00001000, // FORMAT_MESSAGE_FROM_SYSTEM
IntPtr.Zero, er,
0x0409, // US language -- in case of issue, replace with 0
buffer, 2048, IntPtr.Zero);
if (res == 0)
throw new ApplicationException(string.Format("Cannot format message - Error : {0}", res));
string resstring;
fixed (char* ptr = &buffer[0])
{
resstring = new string(ptr);
}
return resstring;
}
static void Main()
{
// Trouble-shooting
// print execution directory
Console.Out.WriteLine("Current directory : {0}", Environment.CurrentDirectory);
Console.Out.WriteLine("Size of IntPtr = {0}", Marshal.SizeOf(IntPtr.Zero));
// first, make sure file exists
string path = #"Troubleshooting_CUDA.dll"; // replace with actual dll name - you can read that on the output of the build
if (!File.Exists(path))
{
Console.Out.WriteLine("Dll could not be found in path, please verify dll is located in the appropriate directory that LoadLibrary may find it");
Environment.Exit(1);
}
// make sure it can be loaded -- open DLL in depends to missing troubleshoot dependencies (may be long to load)
IntPtr lib = LoadLibrary(path);
if (lib == IntPtr.Zero)
{
int code = Marshal.GetLastWin32Error();
string er = ErrorToString(code);
Console.Out.WriteLine("Dll could not be loaded : {0}", er);
Environment.Exit(2);
}
// finally try to get the proc address -- open DLL in depends to see list of symbols (may be long to load)
IntPtr procAddress = GetProcAddress(lib, "Hello_Worldx46Hello_ExternCWrapper_CUDA");
if (procAddress == IntPtr.Zero)
{
int code = Marshal.GetLastWin32Error();
string er = ErrorToString(code);
Console.Out.WriteLine("Could not find symbol in dll : {0}", er);
Environment.Exit(3);
}
cuda.DeviceSynchronize();
HybRunner runner = HybRunner.Cuda().SetDistrib(1, 2);
runner.Wrap(new Hello_World()).Hello();
}
}
If it fails at first step, you may need to change output directory of the CUDA satellite project, and/or execution directory of your application.
Should it fail at second step, you want to verify you execute in x64 (size of IntPtr should be 8), and that dll loads. Dependency walker, you may find here, is a great tool for this purpose, you want the x64 version. NOTE: loading may be very long.
Should it fail at third step, look-up the symbol name with depends, maybe your CUDA satellite project is not up to date.
I'm currently trying to accomplish the following:
For an SDK, which we provide to our customers, we want the SDK-developers to be able to provide external application calls, so that they can insert additional buttons. These buttons than will start an external application or open a file with the default application for it (Word for *.docx for example).
There should be some visual distinction between the different buttons, so our approach is to show the icon of the application to be called.
Now, there are three different kind of calls:
(The strings below would always be the value of ProcessStartInfo.FileName)
Calling an application providing the full application path, possibly with environement vars (e.g. "C:\Program Files\Internet Explorer\iexplore.exe" / "%ProgramFiles%\Internet Explorer\iexplore.exe")
Calling an application providing only the executable name, given the application can be found in the PATH Variable (e.g. "iexplore")
Opening a document, without providing an application to open it (e.g. "D:\test.html")
We are looking for a way, to find the appropriate Icon for any given call. For this we have to find the full application path of the application, which will be executed in any of the three ways above, but before we actually have started the Process
Is there a way to find the full path or the icon of a System.Diagnostics.Process or System.Diagnostics.ProcessStartInfo object, before the process has been started?
Important: We must not start the process before (could have side effects)
Example Code:
var process = new Process
{
StartInfo =
{
//applicationPath could be any of the stated above calls
FileName = Environment.ExpandEnvironmentVariables(applicationPath)
}
};
//we have to find the full path here, but MainModule is null as long as the process object has not yet started
var icon = Icon.ExtractAssociatedIcon(process.MainModule.FullPath)
Solution
Thanks to you guys I found my solution. The project linked here at CodeProject provides a solution for my exact problem, which works equally with programs and files and can provide the icon before starting the process. Thanks for the link #wgraham
If you want your UI to be visually-consistent with the rest of the user's machine, you may want to extract the icon from the file using Icon.ExtractAssociatedIcon(string path). This works under the WinForms/GDI world. Alternatively, this question addresses how to complete it with P/Invoke.
Your first two examples shouldn't be too hard to figure out using Environment.ExpandEnvironmentVariables. Your last one is the tougher one - the best bet seems to be using PInvoke to call AssocCreate. Adapted from the pinvoke page (http://www.pinvoke.net/default.aspx/shlwapi/AssocCreate.html):
public static class GetDefaultProgramHelper
{
public unsafe static string GetDefaultProgram(string ext)
{
try
{
object obj;
AssocCreate(
ref CLSID_QueryAssociations,
ref IID_IQueryAssociations,
out obj);
IQueryAssociations qa = (IQueryAssociations)obj;
qa.Init(
ASSOCF.INIT_DEFAULTTOSTAR,
ext, //".doc",
UIntPtr.Zero, IntPtr.Zero);
int size = 0;
qa.GetString(ASSOCF.NOTRUNCATE, ASSOCSTR.COMMAND,
"open", null, ref size);
StringBuilder sb = new StringBuilder(size);
qa.GetString(ASSOCF.NOTRUNCATE, ASSOCSTR.COMMAND,
"open", sb, ref size);
//Console.WriteLine(".doc is opened by : {0}", sb.ToString());
return sb.ToString();
}
catch(Exception e)
{
if((uint)Marshal.GetHRForException(e) == 0x80070483)
//Console.WriteLine("No command line is associated to .doc open verb.");
return null;
else
throw;
}
}
[DllImport("shlwapi.dll")]
extern static int AssocCreate(
ref Guid clsid,
ref Guid riid,
[MarshalAs(UnmanagedType.Interface)] out object ppv);
[Flags]
enum ASSOCF
{
INIT_NOREMAPCLSID = 0x00000001,
INIT_BYEXENAME = 0x00000002,
OPEN_BYEXENAME = 0x00000002,
INIT_DEFAULTTOSTAR = 0x00000004,
INIT_DEFAULTTOFOLDER = 0x00000008,
NOUSERSETTINGS = 0x00000010,
NOTRUNCATE = 0x00000020,
VERIFY = 0x00000040,
REMAPRUNDLL = 0x00000080,
NOFIXUPS = 0x00000100,
IGNOREBASECLASS = 0x00000200,
INIT_IGNOREUNKNOWN = 0x00000400
}
enum ASSOCSTR
{
COMMAND = 1,
EXECUTABLE,
FRIENDLYDOCNAME,
FRIENDLYAPPNAME,
NOOPEN,
SHELLNEWVALUE,
DDECOMMAND,
DDEIFEXEC,
DDEAPPLICATION,
DDETOPIC,
INFOTIP,
QUICKTIP,
TILEINFO,
CONTENTTYPE,
DEFAULTICON,
SHELLEXTENSION
}
enum ASSOCKEY
{
SHELLEXECCLASS = 1,
APP,
CLASS,
BASECLASS
}
enum ASSOCDATA
{
MSIDESCRIPTOR = 1,
NOACTIVATEHANDLER,
QUERYCLASSSTORE,
HASPERUSERASSOC,
EDITFLAGS,
VALUE
}
[Guid("c46ca590-3c3f-11d2-bee6-0000f805ca57"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IQueryAssociations
{
void Init(
[In] ASSOCF flags,
[In, MarshalAs(UnmanagedType.LPWStr)] string pszAssoc,
[In] UIntPtr hkProgid,
[In] IntPtr hwnd);
void GetString(
[In] ASSOCF flags,
[In] ASSOCSTR str,
[In, MarshalAs(UnmanagedType.LPWStr)] string pwszExtra,
[Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwszOut,
[In, Out] ref int pcchOut);
void GetKey(
[In] ASSOCF flags,
[In] ASSOCKEY str,
[In, MarshalAs(UnmanagedType.LPWStr)] string pwszExtra,
[Out] out UIntPtr phkeyOut);
void GetData(
[In] ASSOCF flags,
[In] ASSOCDATA data,
[In, MarshalAs(UnmanagedType.LPWStr)] string pwszExtra,
[Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)] out byte[] pvOut,
[In, Out] ref int pcbOut);
void GetEnum(); // not used actually
}
static Guid CLSID_QueryAssociations = new Guid("a07034fd-6caa-4954-ac3f-97a27216f98a");
static Guid IID_IQueryAssociations = new Guid("c46ca590-3c3f-11d2-bee6-0000f805ca57");
}
You could call this using string filePathToProgram = GetDefaultProgramHelper.GetDefaultProgram(".docx");
The Process class can do exactly what you want.
Environment Variables like %ProgramFiles% need to be expanded first with Environment.ExpandEnvironmentVariables(string).
1.
using System.IO;
using System.Diagnostics;
string iexplore = #"C:\Program Files\Internet Explorer\iexplore.exe");
string iexploreWithEnvVars = Environment.ExpandEnvironmentVariables(#"%ProgramFiles%\Internet Explorer\iexplore.exe");
2.
public static string FindFileInPath(string name)
{
foreach (string path in Environment.ExpandEnvironmentVariables("%path%").Split(';'))
{
string filename;
if (File.Exists(filename = Path.Combine(path, name)))
{
return filename; // returns the absolute path if the file exists
}
}
return null; // will return null if it didn't find anything
}
3.
Process.Start("D:\\test.html");
You want to put your code into try-catch blocks as well since Process.Start will throw an Exception if the file doesn't exist.
Edit: Updated the code, thanks to Dan Field for pointing out that I missed the meaning of the question. :/
I've implemented a SetupAPI wrapper in C# that enumerates devices based on GUIDs. I have, more or less, converted the DevCon c++ example code found on MSDN to C# (Yes I very much know that this is all veyr painfully to do in .NET, but it's fun and a challenge).
I'm able to get all the appropriate information about a certain device, but a problem occurred when I reached the "SetupScanFileQueue" method.
I cant seem to be able to make the "SetupScanFileQueue" to call my callback. The method returns true, so it seems to be working.
My end goal is to get the driver files of the specific device.
Additional information:
The files appear to be added to the FileQueue correctly, I get this popup window that seems to copy the correct files.
// create a file queue so we can look at this queue later
var queueHandler = SetupOpenFileQueue();
if (queueHandler == IntPtr.Zero)
return false;
// modify flags to indicate we're providing our own queue
var deviceInstallParams = new SP_DEVINSTALL_PARAMS();
deviceInstallParams.cbSize = Marshal.SizeOf(deviceInstallParams);
if (!SetupDiGetDeviceInstallParams(handle, ref devInfo, ref deviceInstallParams))
{
error = Marshal.GetLastWin32Error();
return false;
}
// we want to add the files to the file queue, not install them!
deviceInstallParams.FileQueue = queueHandler;
deviceInstallParams.Flags |= DI_NOVCP;
if (!SetupDiGetDeviceInstallParams(handle, ref devInfo, ref deviceInstallParams))
{
error = Marshal.GetLastWin32Error();
return false;
}
// now fill queue with files that are to be installed
// this involves all class/co-installers
if (!SetupDiCallClassInstaller(DIF_INSTALLDEVICEFILES, handle, ref devInfo))
{
error = Marshal.GetLastWin32Error();
return false;
}
// we now have a list of delete/rename/copy files
// iterate the copy queue twice - 1st time to get # of files
// 2nd time to get files
// (WinXP has API to get # of files, but we want this to work
// on Win2k too)
var scanResult = 0;
var count = 0;
var callback = new PSP_FILE_CALLBACK(PSP_FILEFOUND_CALLBACK);
var t = SetupScanFileQueue(queueHandler, SPQ_SCAN_USE_CALLBACK, IntPtr.Zero,
callback, ref count, ref scanResult);
SetupDiDestroyDriverInfoList(handle, ref devInfo, SPDIT_CLASSDRIVER);
if (queueHandler != IntPtr.Zero)
SetupCloseFileQueue(queueHandler);
The definition of my Callback is as follows:
public delegate uint PSP_FILE_CALLBACK(uint context, uint notifaction, IntPtr param1, IntPtr param2);
public static uint PSP_FILEFOUND_CALLBACK(uint context, uint notifaction, IntPtr param1, IntPtr param2)
{
//This callback is never triggered
return 0;
}
Does anyone have any suggestions to what I'm doing wrong in the "SetupScanFileQueue" function, and why the callback is never called?
Any help is very much appreciated!
Edit:
I should also have added the DllImport for the SetupScanFileQueue function:
[DllImport("setupapi.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
private static extern uint SetupScanFileQueue(IntPtr QueueHandle,
int Flags,
IntPtr Window,
PSP_FILE_CALLBACK CallbackRoutine,
int CallbackContext,
out int ScanResult
);
I've also tried it without the CallingConvention.
I am using a FileSystemWatcher to monitor a folder. But when there is some event happening in the directory, I don't know how to search who made a impact on that file. I tried to use EventLog. It just couldn't work. Is there another way to do it?
I cant remember where I found this code but its an alternative to using pInvoke which I think is a bit overkill for this task. Use the FileSystemWatcher to watch the folder and when an event fires you can work out which user made the file change using this code:
private string GetSpecificFileProperties(string file, params int[] indexes)
{
string fileName = Path.GetFileName(file);
string folderName = Path.GetDirectoryName(file);
Shell32.Shell shell = new Shell32.Shell();
Shell32.Folder objFolder;
objFolder = shell.NameSpace(folderName);
StringBuilder sb = new StringBuilder();
foreach (Shell32.FolderItem2 item in objFolder.Items())
{
if (fileName == item.Name)
{
for (int i = 0; i < indexes.Length; i++)
{
sb.Append(objFolder.GetDetailsOf(item, indexes[i]) + ",");
}
break;
}
}
string result = sb.ToString().Trim();
//Protection for no results causing an exception on the `SubString` method
if (result.Length == 0)
{
return string.Empty;
}
return result.Substring(0, result.Length - 1);
}
Shell32 is a reference to the DLL: Microsoft Shell Controls And Automation - its a COM reference
Here is some example's of how you call the method:
string Type = GetSpecificFileProperties(filePath, 2);
string ObjectKind = GetSpecificFileProperties(filePath, 11);
DateTime CreatedDate = Convert.ToDateTime(GetSpecificFileProperties(filePath, 4));
DateTime LastModifiedDate = Convert.ToDateTime(GetSpecificFileProperties(filePath, 3));
DateTime LastAccessDate = Convert.ToDateTime(GetSpecificFileProperties(filePath, 5));
string LastUser = GetSpecificFileProperties(filePath, 10);
string ComputerName = GetSpecificFileProperties(filePath, 53);
string FileSize = GetSpecificFileProperties(filePath, 1);
Or get multiple comma separated properties together:
string SizeTypeAndLastModDate = GetSpecificFileProperties(filePath, new int[] {1, 2, 3});
Note: This solution has been tested on Windows 7 and Windows 10. It wont work unless running in a STA as per Exception when using Shell32 to get File extended properties and you will see the following error:
Unable to cast COM object of type 'Shell32.ShellClass' to interface type 'Shell32.IShellDispatch6'
You need to enable auditing on the file system (and auditing is only available on NTFS). You do this by applying a group policy or local security policy. You will also have to enable auditing on the file you want to monitor. You do it the same way as you modify the permissions on the file.
Auditing events are then written to the security event log. You will have to monitor this event log for the auditing events you are interested in. One way to do this is to create a scheduled task that starts an application when the events you are interested in are logged. Starting a new process for each event is only viable if events aren't logged at a very high rate though. Otherwise you will likely experience performance problems.
Basically, you don't want to look at the contents or attributes of the file (which the shell function GetFileDetails does). Also, you don't want to use a file sharing API to get the network user that has the file open (which NetGetFileInfo does). You want to know the user of the process that last modified the file. This information is not normally recorded by Windows because it would require too many resources to do that for all file activities. Instead you can selectively enable auditing for specific users doing specifc actions on specific files (and folders).
It seems that you'll need to invoke Windows API functions to get what you want, which involves PInvoke. Some people on another forum have been looking into it and figured something out, you can find their solution here. However, it seems to work only with files on network shares (not on your local machine).
For future reference, this is the code posted by dave4dl:
[DllImport("Netapi32.dll", SetLastError = true)]
static extern int NetApiBufferFree(IntPtr Buffer);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)]
struct FILE_INFO_3
{
public int fi3_id;
public int fi3_permission;
public int fi3_num_locks;
public string fi3_pathname;
public string fi3_username;
}
[DllImport("netapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern int NetFileEnum(
string servername,
string basepath,
string username,
int level,
ref IntPtr bufptr,
int prefmaxlen,
out int entriesread,
out int totalentries,
IntPtr resume_handle
);
[DllImport("netapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern int NetFileGetInfo(
string servername,
int fileid,
int level,
ref IntPtr bufptr
);
private int GetFileIdFromPath(string filePath)
{
const int MAX_PREFERRED_LENGTH = -1;
int dwReadEntries;
int dwTotalEntries;
IntPtr pBuffer = IntPtr.Zero;
FILE_INFO_3 pCurrent = new FILE_INFO_3();
int dwStatus = NetFileEnum(null, filePath, null, 3, ref pBuffer, MAX_PREFERRED_LENGTH, out dwReadEntries, out dwTotalEntries, IntPtr.Zero);
if (dwStatus == 0)
{
for (int dwIndex = 0; dwIndex < dwReadEntries; dwIndex++)
{
IntPtr iPtr = new IntPtr(pBuffer.ToInt32() + (dwIndex * Marshal.SizeOf(pCurrent)));
pCurrent = (FILE_INFO_3)Marshal.PtrToStructure(iPtr, typeof(FILE_INFO_3));
int fileId = pCurrent.fi3_id;
//because of the path filter in the NetFileEnum function call, the first (and hopefully only) entry should be the correct one
NetApiBufferFree(pBuffer);
return fileId;
}
}
NetApiBufferFree(pBuffer);
return -1; //should probably do something else here like throw an error
}
private string GetUsernameHandlingFile(int fileId)
{
string defaultValue = "[Unknown User]";
if (fileId == -1)
{
return defaultValue;
}
IntPtr pBuffer_Info = IntPtr.Zero;
int dwStatus_Info = NetFileGetInfo(null, fileId, 3, ref pBuffer_Info);
if (dwStatus_Info == 0)
{
IntPtr iPtr_Info = new IntPtr(pBuffer_Info.ToInt32());
FILE_INFO_3 pCurrent_Info = (FILE_INFO_3)Marshal.PtrToStructure(iPtr_Info, typeof(FILE_INFO_3));
NetApiBufferFree(pBuffer_Info);
return pCurrent_Info.fi3_username;
}
NetApiBufferFree(pBuffer_Info);
return defaultValue; //default if not successfull above
}
private string GetUsernameHandlingFile(string filePath)
{
int fileId = GetFileIdFromPath(filePath);
return GetUsernameHandlingFile(fileId);
}
This has been discussed many times. My answer from the same question:
You can't do this asynchronously with FileSystemWatcher, however you can do this synchronously using file system filter driver. The driver lets you get the user name of the account performing the operation.
Use code posted by dave4dl and update declare struct FILE_INFO_3 as following,
you can monitor user name of create and update file action(It is like to combination of FileSystemWatcher and OpenFiles.exe's functions of FileSharing Server)
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct FILE_INFO_3
{
public int fi3_id;
public int fi3_permission;
public int fi3_num_locks;
[MarshalAs(UnmanagedType.LPWStr)]
public string fi3_pathname;
[MarshalAs(UnmanagedType.LPWStr)]
public string fi3_username;
}
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.