Clipboard: Copy and receive array - c#

In a Winform application I have an array of this format:
float[][,]
I need to copy it via the clipboard to another Winform app.
How could I achieve this?
Thanks

Short answer: Using the System.Windows.Forms.Clipboard is simple.
Use Clipboard.SetData("CustomDataName", nonStandardData); to put your custom data on the clipboard and use var clipboardData = (float[][,])Clipboard.GetData("CustomDataName"); to get your custom data off the clipboard. You should check if the data is available on the clipboard using Clipboard.ContainsData("CustomDataName")
Note: By default the Windows clipboard only contains one entry at
a time be it an "image", "text" or "custom data". By putting your
custom data on the clipboard the previous data contained on the
clipboard will be gone.
The clipboard is best used for sharing more universal data between
unrelated programs, such as an image which can be pasted into a web browser or image editor. A grid of csv values which can be pasted into different data capturing and spreadsheet programs. float[][,] on the other hand has no real meaning other than to your software.
Long Answer (Here is an example):
// A Format/"Type" name for your clipboard data
static readonly string JOE_SOAP_DATA_V1 =
"JOE_SOAP_DATA_V1 {F1193E97-0B29-4ACD-935C-835A8EDFBB9D}";
// Some dummy data used to demonstrate
private readonly float[][,] JoeSoapData = new float[3][,]
{
new float[,]{{54,90},{34,56}},
new float[,]
{
{
// random values to create some variation
// will be different each time program is started
(float)new Random().NextDouble() * 2,
(float)new Random().NextDouble() * 6
},
{
(float)new Random().NextDouble() * 4,
(float)new Random().NextDouble() * 7
},
{
(float)new Random().NextDouble() * 4,
(float)new Random().NextDouble() * 7
}
},
new float[,]{{54,90},{34,56}}
};
private string JoeSoapDataToString(float[][,] data)
{
var sb = new StringBuilder();
foreach(var x in data)
{
foreach (var y in x)
sb.Append(y).Append(", ");
sb.Append('\n');
}
return sb.ToString();
}
private void BtnCopyToClipboard_Click(object sender, EventArgs e)
{
// On button click add the dummy data to the clipboard
Clipboard.SetData(JOE_SOAP_DATA_V1, JoeSoapData);
}
private void BtnPasteFromClipboard_Click(object sender, EventArgs e)
{
// Check if the clipboard contains our unique Format and notify the user if it does not
if (!Clipboard.ContainsData(JOE_SOAP_DATA_V1))
{
MessageBox.Show($"No {nameof(JOE_SOAP_DATA_V1)} clipboard data found :(");
return;
}
// extract the clipboard data and show the user
var clipboardData = (float[][,])Clipboard.GetData(JOE_SOAP_DATA_V1);
// show the copied data
LblFromClipboard.Text = "Data from clipboard\n" + JoeSoapDataToString(clipboardData);
}
private void Form1_Load(object sender, EventArgs e)
{
LblDataToCopy.Text = "Data that will be copied to the clipboard\n"
+ JoeSoapDataToString(JoeSoapData);
}

You can use AddClipboardFormatListener (for listening the clipboard update message) combined with Clipboard class to copy the data.
Receiver app:
public partial class ReceiverAppMainForm : Form
{
public ReceiverAppMainForm()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
ClipboardWrapper.AddClipboardFormatListener(Handle);
base.OnLoad(e);
}
protected override void OnClosing(CancelEventArgs e)
{
ClipboardWrapper.RemoveClipboardFormatListener(Handle);
base.OnClosing(e);
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == ClipboardWrapper.WM_CLIPBOARDUPDATE)
{
var data = Clipboard.GetDataObject();
if (data != null && data.GetDataPresent("clipboard-demoapp-data"))
{
var array = (float[][,])data.GetData("clipboard-demoapp-data");
// TODO: use the shared data
}
}
}
internal static class ClipboardWrapper
{
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool AddClipboardFormatListener(IntPtr hwnd);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
internal const int WM_CLIPBOARDUPDATE = 0x031D;
}
}
Sender app:
public partial class SenderAppMainForm : Form
{
// ... ignored for brevity
private static void CopyAppDataToClipboard(float[][,] data)
{
// variant #1 (to avoid duplication)
var dataObject = new DataObject();
dataObject.SetData("clipboard-demoapp-data", data);
Clipboard.SetDataObject(dataObject, false, 10, 100);
// variant #2 (the event is sending twice on my computer running Win10)
//Clipboard.SetData("cliboard-demoapp-data", data);
}
}

Related

get a return also if a part of a sentence is copied instead of the whole sentence c#

WHAT DO I WANT?
I want that if you take a part of the question sentence that you get the same return than if you copy the entire sentence. I was already watching the StartWith method but I do not know how to use it. Does anyone have a solution for this? This is my code. Thanks in advance.
namespace test
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//Invoke a clipboard monitor
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SetClipboardViewer(IntPtr hWndNewViewer);
private IntPtr _ClipboardViewerNext;
//Make some global variables so we can access them somewhere else later
//This will store all Questions and Answers
//In here will be the Questions and Answers
List<question> questionList = new List<question>();
private void Form1_Load(object sender, EventArgs e)
{
//Set our application as a clipboard viewer
_ClipboardViewerNext = SetClipboardViewer(this.Handle);
//Add question/answer to list
question newQuestion = new question("When a computer is being assembled, which action can be taken to help eliminate cable clutter within a computer case?", "Install a modular power supply.*");
questionList.Add(newQuestion);
newQuestion = new question("vraag2", "antwoord2");
questionList.Add(newQuestion);
newQuestion = new question("vraag3", "antwoord3");
questionList.Add(newQuestion);
}
private void getAnswer(string clipboardText)
{
//Loop through all questions and answers
foreach (question q in questionList)
{
//If we have found an answer that is exactly the same show an Notification
if (q._question == clipboardText)
{
showNotification(q._question, q._answer);
}
}
}
private void showNotification(string question, string answer)
{
notifyIcon1.Icon = SystemIcons.Exclamation;
notifyIcon1.BalloonTipTitle = question;
notifyIcon1.BalloonTipText = answer;
notifyIcon1.BalloonTipIcon = ToolTipIcon.Error;
notifyIcon1.ShowBalloonTip(100);
Clipboard.Clear();
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
{
const int WM_DRAWCLIPBOARD = 0x308;
if (m.Msg == WM_DRAWCLIPBOARD) {
getAnswer(Clipboard.GetText());
}
}
}
}
}
Thanks in advance
First remove any 'space' from the questions and store them in a separate field. For example:
string question="how are you today";
string questionWithNoSpace=question.Replace(" ","");//Which makes the question like this:"howareyoutoday".
Now get the question from the clip board and split in by 'space' like this:
string userQuestion ="how are you";
list<string> parts=userQuestion.Splite(" "); //Which returns a list containing {"how","are","you"}
if (parts.All(a=>questionWithNoSpace.Contains(a)))
{
return "Blah! that is your question";
}
Remember to convert everything into lower case

Using User32.dll SendMessage to handle the download popup

I'm navigating through a webapp using WatiN and I needed to download a document. However, I've found out that WatiN doesn't support the download with Internet Explorer 11. That's why I'm trying to do it using the method described here :
How to enable automatic downloads in IE11 (the top answer)
Using User32.dll SendMessage To Send Keys With ALT Modifier
Basically I'm calling user32.dll to handle the small popup DL window (F6 to select it, tab, enter, etc.)
However nothing happens, the popup isn't responding to my commands.
Is this the right way to do it ?
My code looks like this :
//Before my method
[DllImport("user32.dll")]
public static extern IntPtr SetActiveWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, uint wParam, uint lParam);
private void button1_Click(object sender, EventArgs e)
{
//some code....
IE myPopup = IE.AttachTo<IE>(Find.ByUrl("http://abcd.do"));
FileDownloadHandler fileDownloadHandler = new FileDownloadHandler("test");
myPopup.AddDialogHandler(fileDownloadHandler);
myPopup.Link(Find.ByUrl("httpabcdefg.do")).ClickNoWait();
ushort action = (ushort)260; //WM_SYSKEYDOWN
System.Threading.Thread.Sleep(2000);
ushort key = (ushort)System.Windows.Forms.Keys.F6;
ushort key2 = (ushort)System.Windows.Forms.Keys.Tab;
ushort key3 = (ushort)System.Windows.Forms.Keys.Enter;
ushort key4 = (ushort)System.Windows.Forms.Keys.Right;
SendMessage(myPopup.hWnd, action, key, 0);
SendMessage(myPopup.hWnd, action, key2, 0);
SendMessage(myPopup.hWnd, action, key3, 0);
SendMessage(myPopup.hWnd, action, key4, 0);
SendMessage(myPopup.hWnd, action, key4, 0);
SendMessage(myPopup.hWnd, action, key3, 0);
}
I know the popup (myPopup) is handled correctly, I can click on it using watin.
A couple of things are unclear to me, and I put the action WM_SYSKEYDOWN as default, to follow the example cited above.
Thanks in advance for any kind of help !
--------------EDIT-----------------
I managed to do it, I know it's not optimal at all but I dropped the Dllimport and the SendMessage all together and used SendKeys.Send instead. Just like that :
private void button1_Click(object sender, EventArgs e)
{
//some code....
IE myPopup = IE.AttachTo<IE>(Find.ByUrl("http://abcd.do"));
FileDownloadHandler fileDownloadHandler = new FileDownloadHandler("test");
myPopup.AddDialogHandler(fileDownloadHandler);
myPopup.Link(Find.ByUrl("httpabcdefg.do")).ClickNoWait();
SendKeys.Send("{F6}");
SendKeys.Send("{ENTER}");
SendKeys.Send("{RIGHT}");
SendKeys.Send("{RIGHT}");
SendKeys.Send("{ENTER}");
}
The code that you are using is not a proper way to automate and WatiN does not completely interact with Windows controls and help in windows is very little. I had the same problem in handling the windows controls as we had multiple versions of IE. IE 9.0 and higher have different file handling when compared to <= IE 8.0 version. The below code works fine for IE 9.0 and higher. Please make sure proper references are added (refer using's).
Refer below code and modify it as per you requirement.
using System.Threading;
using System.Windows.Automation;
using WatiN.Core;
using WatiN.Core.Native.Windows;
namespace TestFramework.Util
{
public static class WindowsHelper
{
#region Public Methods
/// <summary>
/// Download IE file.
/// </summary>
/// <param name="action">Action can be Save/Save As/Open/Cancel.</param>
/// <param name="path">Path where file needs to be saved (for Save As function).</param>
public static void DownloadIEFile(string action, string path = "", string regexPatternToMatch = "")
{
Browser browser = null;
if (Utility.Browser != null) // Utility.Browser is my WatiN browser instance.
{
if (string.IsNullOrEmpty(regexPatternToMatch))
{
browser = Utility.Browser;
}
else
{
Utility.Wait(() => (browser = Browser.AttachTo<IE>(Find.ByUrl(new System.Text.RegularExpressions.Regex(regexPatternToMatch)))) != null);
}
}
else
{
return;
}
// If doesn't work try to increase sleep interval or write your own waitUntill method
Thread.Sleep(3000);
// See information here (http://msdn.microsoft.com/en-us/library/windows/desktop/ms633515(v=vs.85).aspx)
Window windowMain = null;
Utility.Wait(() => (windowMain = new Window(NativeMethods.GetWindow(browser.hWnd, 5))).ProcessID != 0);
TreeWalker trw = new TreeWalker(Condition.TrueCondition);
AutomationElement mainWindow = trw.GetParent(AutomationElement.FromHandle(browser.hWnd));
Window windowDialog = null;
Utility.Wait(() => (windowDialog = new Window(NativeMethods.GetWindow(windowMain.Hwnd, 5))).ProcessID != 0);
windowDialog.SetActivate();
AutomationElementCollection amc = null;
Utility.Wait(() => (amc = AutomationElement.FromHandle(windowDialog.Hwnd).FindAll(TreeScope.Children, Condition.TrueCondition)).Count > 1);
foreach (AutomationElement element in amc)
{
// You can use "Save ", "Open", ''Cancel', or "Close" to find necessary button Or write your own enum
if (element.Current.Name.Equals(action))
{
// If doesn't work try to increase sleep interval or write your own waitUntil method
// WaitUntilButtonExsist(element,100);
Thread.Sleep(1000);
AutomationPattern[] pats = element.GetSupportedPatterns();
// Replace this for each if you need 'Save as' with code bellow
foreach (AutomationPattern pat in pats)
{
// '10000' button click event id
if (pat.Id == 10000)
{
InvokePattern click = (InvokePattern)element.GetCurrentPattern(pat);
click.Invoke();
}
}
}
else if (element.Current.Name.Equals("Save") && action == "Save As")
{
AutomationElementCollection bmc = element.FindAll(TreeScope.Children, Automation.ControlViewCondition);
InvokePattern click1 = (InvokePattern)bmc[0].GetCurrentPattern(AutomationPattern.LookupById(10000));
click1.Invoke();
Thread.Sleep(1000);
AutomationElementCollection main = mainWindow.FindAll(TreeScope.Children, Condition.TrueCondition);
foreach (AutomationElement el in main)
{
if (el.Current.LocalizedControlType == "menu")
{
// First array element 'Save', second array element 'Save as', third second array element 'Save and open'
InvokePattern clickMenu = (InvokePattern)
el.FindAll(TreeScope.Children, Condition.TrueCondition)[1].GetCurrentPattern(AutomationPattern.LookupById(10000));
clickMenu.Invoke();
Thread.Sleep(1000);
ControlSaveDialog(mainWindow, path);
break;
}
}
}
}
}
/// <summary>
/// Control for save dialog.
/// </summary>
/// <param name="mainWindow">Main window.</param>
/// <param name="path">Path.</param>
private static void ControlSaveDialog(AutomationElement mainWindow, string path)
{
// Obtain the save as dialog
var saveAsDialog = mainWindow
.FindFirst(TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "Save As"));
// Get the file name box
var saveAsText = saveAsDialog
.FindFirst(TreeScope.Descendants,
new AndCondition(
new PropertyCondition(AutomationElement.NameProperty, "File name:"),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit)))
.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;
// Fill the filename box
saveAsText.SetValue(path);
Thread.Sleep(500);
Utility.PressKey("LEFT");
Utility.PressKey("LEFT");
Thread.Sleep(1000);
// Find the save button
var saveButton =
saveAsDialog.FindFirst(TreeScope.Descendants,
new AndCondition(
new PropertyCondition(AutomationElement.NameProperty, "Save"),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button)));
// Invoke the button
var pattern = saveButton.GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;
pattern.Invoke();
}
#endregion
}
}
public static class Utility
{
public static IE Browser { get; set; }
// Wait specified number of seconds
public static void Wait(int seconds)
{
System.Threading.Thread.Sleep(seconds * 1000);
}
// Wait for condition to evaluate true, timeout after 30 seconds
public static void Wait(Func<bool> condition)
{
int count = 0;
while (!condition() && count < 30)
{
System.Threading.Thread.Sleep(1000);
count++;
}
}
//Send tab key press to browser
public static void PressTab()
{
System.Windows.Forms.SendKeys.SendWait("{TAB}");
System.Threading.Thread.Sleep(300);
}
//Send specified key press to browser
public static void PressKey(string keyname)
{
System.Windows.Forms.SendKeys.SendWait("{" + keyname.ToUpper() + "}");
System.Threading.Thread.Sleep(300);
}
}

generate different captcha in one session

how i want to generate a captcha that is different with each other in one session?
for example :
i have a application form that has a captcha before the user can proceed to next step. the problem is, if I open another same application form in the new windows tab, it will generate the same captcha as the first application form. what i need is different captcha value.
help me with solving this problem please...tq
i'm using asp.net.
here the sample code
to generate code
private string GenerateRandomCode()
{
string s = "";
for (int i = 0; i < 6; i++)
s = String.Concat(s, random.Next(10).ToString());
return s;
}
to generate captcha image
namespace OneStepContactMe.Layouts.OneStepContactMe
{
public partial class captchapage : UnsecuredLayoutsPageBase
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
if (Session["Map"] == null)
{
Session["Map"] = GenerateRandomCode();
}
// Create a CAPTCHA image using the text stored in the Session object.
CaptchaImage ci = new CaptchaImage(this.Session["Map"].ToString(), 200, 50, "Century Schoolbook");
// Change the response headers to output a JPEG image.
this.Response.Clear();
this.Response.ContentType = "image/jpeg";
// Write the image to the response stream in JPEG format.
ci.Image.Save(this.Response.OutputStream, ImageFormat.Jpeg);
// Dispose of the CAPTCHA image object.
ci.Dispose();
}
else {
}
}
/// <summary>
/// generate random 6 digit
/// </summary>
/// <returns> string of 6 digits </returns>
private string GenerateRandomCode()
{
Random random = new Random();
string s = "";
for (int i = 0; i < 6; i++)
s = String.Concat(s, random.Next(10).ToString());
return s;
}
//to allow anonymous access to this pages
protected override bool AllowAnonymousAccess
{
get
{
return true;
}
}
protected override void OnPreInit(EventArgs e)
{
base.OnPreInit(e);
this.MasterPageFile = "/_catalogs/masterpage/MyCorridor.MemberArea.v1.master";
}
}
Change the Random local variable to member field.
e.g.
public partial class captchapage : UnsecuredLayoutsPageBase
{
private Random random = new Random();
}

Setting FlashVars of AxShockwaveFlash

a program of mine uses AxShockwaveFlash component used as stream player.
The problem is that my code works with most stream-providers (livestream, ustream, own3d.tv) but Justin.TV's player is somewhat problematic.
Before moving on the actual problem let me summarize my code;
Inherited FlashControl - this allows me to override the flashplayer's built-in menu:
public class FlashPlayer : AxShockwaveFlashObjects.AxShockwaveFlash // Customized Flash Player.
{
private const int WM_MOUSEMOVE = 0x0200;
private const int WM_MOUSEWHEEL = 0x020A;
private const int WM_LBUTTONDOWN = 0x0201;
private const int WM_LBUTTONUP = 0x0202;
private const int WM_LBUTTONDBLCLK = 0x0203;
private const int WM_RBUTTONDOWN = 0x0204;
private const int WM_RBUTTONUP = 0x0205;
public new event MouseEventHandler DoubleClick;
public new event MouseEventHandler MouseDown;
public new event MouseEventHandler MouseUp;
public new event MouseEventHandler MouseMove;
public FlashPlayer():base()
{
this.HandleCreated += FlashPlayer_HandleCreated;
}
void FlashPlayer_HandleCreated(object sender, EventArgs e)
{
this.AllowFullScreen = "true";
this.AllowNetworking = "all";
this.AllowScriptAccess = "always";
}
protected override void WndProc(ref Message m) // Override's the WndProc and disables Flash activex's default right-click menu and if exists shows the attached ContextMenuStrip.
{
if (m.Msg == WM_LBUTTONDOWN)
{
if (this.MouseDown != null) this.MouseDown(this, new MouseEventArgs(System.Windows.Forms.MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0));
}
else if (m.Msg == WM_LBUTTONUP)
{
if (this.MouseUp != null) this.MouseUp(this, new MouseEventArgs(System.Windows.Forms.MouseButtons.None, 0, Cursor.Position.X, Cursor.Position.Y, 0));
}
else if (m.Msg == WM_MOUSEMOVE)
{
if (this.MouseMove != null) this.MouseMove(this, new MouseEventArgs(System.Windows.Forms.MouseButtons.None, 0, Cursor.Position.X, Cursor.Position.Y, 0));
}
else if (m.Msg == WM_RBUTTONDOWN)
{
if (this.ContextMenuStrip != null) this.ContextMenuStrip.Show(Cursor.Position.X, Cursor.Position.Y);
m.Result = IntPtr.Zero;
return;
}
else if (m.Msg == WM_LBUTTONDBLCLK)
{
if (this.DoubleClick != null) this.DoubleClick(this, new MouseEventArgs(System.Windows.Forms.MouseButtons.Left, 2, Cursor.Position.X, Cursor.Position.Y, 0));
m.Result = IntPtr.Zero;
return;
}
base.WndProc(ref m);
}
}
Player window code: (Player is an instance of FlashPlayer)
private void Player_Load(object sender, EventArgs e)
{
try
{
this.Text = string.Format("Stream: {0}", this._stream.Name); // set the window title.
this.Player.LoadMovie(0, this._stream.Movie); // load the movie.
if (this._stream.ChatAvailable && Settings.Instance.AutomaticallyOpenChat) this.OpenChatWindow();
}
catch (Exception exc)
{
// log stuff.
}
}
So this works great for livestream.com, ustream.com, own3d.tv but when it come's to justin.tv's player i'm getting a 1337 error (invalid embed code). So i tried to ask them for support but could't get a valid answer.
_stream.movie variable actually holds a valid URL for the stream source like;
http://cdn.livestream.com/grid/LSPlayer.swf?channel=%slug%&autoPlay=true (livestream sample)
or
http://www.justin.tv/widgets/live_embed_player.swf?channel=%slug%&auto_play=true&start_volume=100 (justin.tv sample)
Tried to urlencode the 'channel=%slug%&auto_play=true&start_volume=100' part for justin.tv but that did not work also.
So i started trying some work-arounds which at first place i thought setting flashVars variable of the control.
But i've a strange problem there, whenever i try to set flashVars variable it never get's set. I found a sample screenshot on the issue;
So if i was able to set the flashVariables may be i could work-around the justin.tv player's error. Btw, i also tried setting variables using Player.SetVariable(key,value) - that didn't work also.
Notes:
I'm running on .net 4.0 client profile.
Using the Flash10l.ocx.
Have generated the AxShockwaveFlashObjects.dll, ShockwaveFlashObjects.dll wrappers using "aximp.exe –source "C:\WINDOWS\system32\Macromed\Flash\Flash10l.ocx"
I recently had an issue with making justin.tv work, but in the end it was as simple as
axShockwaveFlash1.FlashVars = "auto_play=true&channel=adventuretimed&start_volume=25";
axShockwaveFlash1.Movie = "http://www.justin.tv/widgets/live_embed_player.swf";
and it works perfectly

Programmatically Determine a Duration of a Locked Workstation?

How can one determine, in code, how long the machine is locked?
Other ideas outside of C# are also welcome.
I like the windows service idea (and have accepted it) for simplicity and cleanliness, but unfortunately I don't think it will work for me in this particular case. I wanted to run this on my workstation at work rather than home (or in addition to home, I suppose), but it's locked down pretty hard courtesy of the DoD. That's part of the reason I'm rolling my own, actually.
I'll write it up anyway and see if it works. Thanks everyone!
I hadn't found this before, but from any application you can hookup a SessionSwitchEventHandler. Obviously your application will need to be running, but so long as it is:
Microsoft.Win32.SystemEvents.SessionSwitch += new Microsoft.Win32.SessionSwitchEventHandler(SystemEvents_SessionSwitch);
void SystemEvents_SessionSwitch(object sender, Microsoft.Win32.SessionSwitchEventArgs e)
{
if (e.Reason == SessionSwitchReason.SessionLock)
{
//I left my desk
}
else if (e.Reason == SessionSwitchReason.SessionUnlock)
{
//I returned to my desk
}
}
I would create a Windows Service (a visual studio 2005 project type) that handles the OnSessionChange event as shown below:
protected override void OnSessionChange(SessionChangeDescription changeDescription)
{
if (changeDescription.Reason == SessionChangeReason.SessionLock)
{
//I left my desk
}
else if (changeDescription.Reason == SessionChangeReason.SessionUnlock)
{
//I returned to my desk
}
}
What and how you log the activity at that point is up to you, but a Windows Service provides quick and easy access to windows events like startup, shutdown, login/out, along with the lock and unlock events.
The solution below uses the Win32 API. OnSessionLock is called when the workstation is locked, and OnSessionUnlock is called when it is unlocked.
[DllImport("wtsapi32.dll")]
private static extern bool WTSRegisterSessionNotification(IntPtr hWnd,
int dwFlags);
[DllImport("wtsapi32.dll")]
private static extern bool WTSUnRegisterSessionNotification(IntPtr
hWnd);
private const int NotifyForThisSession = 0; // This session only
private const int SessionChangeMessage = 0x02B1;
private const int SessionLockParam = 0x7;
private const int SessionUnlockParam = 0x8;
protected override void WndProc(ref Message m)
{
// check for session change notifications
if (m.Msg == SessionChangeMessage)
{
if (m.WParam.ToInt32() == SessionLockParam)
OnSessionLock(); // Do something when locked
else if (m.WParam.ToInt32() == SessionUnlockParam)
OnSessionUnlock(); // Do something when unlocked
}
base.WndProc(ref m);
return;
}
void OnSessionLock()
{
Debug.WriteLine("Locked...");
}
void OnSessionUnlock()
{
Debug.WriteLine("Unlocked...");
}
private void Form1Load(object sender, EventArgs e)
{
WTSRegisterSessionNotification(this.Handle, NotifyForThisSession);
}
// and then when we are done, we should unregister for the notification
// WTSUnRegisterSessionNotification(this.Handle);
I know this is an old question but i have found a method to get the Lock State for a given session.
I found my answer here but it was in C++ so i translated as much as i can to C# to get the Lock State.
So here goes:
static class SessionInfo {
private const Int32 FALSE = 0;
private static readonly IntPtr WTS_CURRENT_SERVER = IntPtr.Zero;
private const Int32 WTS_SESSIONSTATE_LOCK = 0;
private const Int32 WTS_SESSIONSTATE_UNLOCK = 1;
private static bool _is_win7 = false;
static SessionInfo() {
var os_version = Environment.OSVersion;
_is_win7 = (os_version.Platform == PlatformID.Win32NT && os_version.Version.Major == 6 && os_version.Version.Minor == 1);
}
[DllImport("wtsapi32.dll")]
private static extern Int32 WTSQuerySessionInformation(
IntPtr hServer,
[MarshalAs(UnmanagedType.U4)] UInt32 SessionId,
[MarshalAs(UnmanagedType.U4)] WTS_INFO_CLASS WTSInfoClass,
out IntPtr ppBuffer,
[MarshalAs(UnmanagedType.U4)] out UInt32 pBytesReturned
);
[DllImport("wtsapi32.dll")]
private static extern void WTSFreeMemoryEx(
WTS_TYPE_CLASS WTSTypeClass,
IntPtr pMemory,
UInt32 NumberOfEntries
);
private enum WTS_INFO_CLASS {
WTSInitialProgram = 0,
WTSApplicationName = 1,
WTSWorkingDirectory = 2,
WTSOEMId = 3,
WTSSessionId = 4,
WTSUserName = 5,
WTSWinStationName = 6,
WTSDomainName = 7,
WTSConnectState = 8,
WTSClientBuildNumber = 9,
WTSClientName = 10,
WTSClientDirectory = 11,
WTSClientProductId = 12,
WTSClientHardwareId = 13,
WTSClientAddress = 14,
WTSClientDisplay = 15,
WTSClientProtocolType = 16,
WTSIdleTime = 17,
WTSLogonTime = 18,
WTSIncomingBytes = 19,
WTSOutgoingBytes = 20,
WTSIncomingFrames = 21,
WTSOutgoingFrames = 22,
WTSClientInfo = 23,
WTSSessionInfo = 24,
WTSSessionInfoEx = 25,
WTSConfigInfo = 26,
WTSValidationInfo = 27,
WTSSessionAddressV4 = 28,
WTSIsRemoteSession = 29
}
private enum WTS_TYPE_CLASS {
WTSTypeProcessInfoLevel0,
WTSTypeProcessInfoLevel1,
WTSTypeSessionInfoLevel1
}
public enum WTS_CONNECTSTATE_CLASS {
WTSActive,
WTSConnected,
WTSConnectQuery,
WTSShadow,
WTSDisconnected,
WTSIdle,
WTSListen,
WTSReset,
WTSDown,
WTSInit
}
public enum LockState {
Unknown,
Locked,
Unlocked
}
[StructLayout(LayoutKind.Sequential)]
private struct WTSINFOEX {
public UInt32 Level;
public UInt32 Reserved; /* I have observed the Data field is pushed down by 4 bytes so i have added this field as padding. */
public WTSINFOEX_LEVEL Data;
}
[StructLayout(LayoutKind.Sequential)]
private struct WTSINFOEX_LEVEL {
public WTSINFOEX_LEVEL1 WTSInfoExLevel1;
}
[StructLayout(LayoutKind.Sequential)]
private struct WTSINFOEX_LEVEL1 {
public UInt32 SessionId;
public WTS_CONNECTSTATE_CLASS SessionState;
public Int32 SessionFlags;
/* I can't figure out what the rest of the struct should look like but as i don't need anything past the SessionFlags i'm not going to. */
}
public static LockState GetSessionLockState(UInt32 session_id) {
IntPtr ppBuffer;
UInt32 pBytesReturned;
Int32 result = WTSQuerySessionInformation(
WTS_CURRENT_SERVER,
session_id,
WTS_INFO_CLASS.WTSSessionInfoEx,
out ppBuffer,
out pBytesReturned
);
if (result == FALSE)
return LockState.Unknown;
var session_info_ex = Marshal.PtrToStructure<WTSINFOEX>(ppBuffer);
if (session_info_ex.Level != 1)
return LockState.Unknown;
var lock_state = session_info_ex.Data.WTSInfoExLevel1.SessionFlags;
WTSFreeMemoryEx(WTS_TYPE_CLASS.WTSTypeSessionInfoLevel1, ppBuffer, pBytesReturned);
if (_is_win7) {
/* Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/ee621019(v=vs.85).aspx
* Windows Server 2008 R2 and Windows 7: Due to a code defect, the usage of the WTS_SESSIONSTATE_LOCK
* and WTS_SESSIONSTATE_UNLOCK flags is reversed. That is, WTS_SESSIONSTATE_LOCK indicates that the
* session is unlocked, and WTS_SESSIONSTATE_UNLOCK indicates the session is locked.
* */
switch (lock_state) {
case WTS_SESSIONSTATE_LOCK:
return LockState.Unlocked;
case WTS_SESSIONSTATE_UNLOCK:
return LockState.Locked;
default:
return LockState.Unknown;
}
}
else {
switch (lock_state) {
case WTS_SESSIONSTATE_LOCK:
return LockState.Locked;
case WTS_SESSIONSTATE_UNLOCK:
return LockState.Unlocked;
default:
return LockState.Unknown;
}
}
}
}
Note: The above code was extracted from a much larger project so if i missed a bit sorry. I havn't got time to test the above code but plan to come back in a week or two to check everything. I only posted it now because i didn't want to forget to do it.
NOTE: This is not an answer, but a (contribution) to Timothy Carter answer, because my reputation doesn't allow me to comment so far.
Just in case somebody tried the code from Timothy Carter's answer and did not get it to work right away in a Windows service, there's one property that need to be set to true in the constructor of the service.
Just add the line in the constructor:
CanHandleSessionChangeEvent = true;
And be sure not to set this property after the service is started otherwise an InvalidOperationException will be thrown.
If you're interested in writing a windows-service to "find" these events, topshelf (the library/framework that makes writing windows services much easier) has a hook.
public interface IMyServiceContract
{
void Start();
void Stop();
void SessionChanged(Topshelf.SessionChangedArguments args);
}
public class MyService : IMyServiceContract
{
public void Start()
{
}
public void Stop()
{
}
public void SessionChanged(SessionChangedArguments e)
{
Console.WriteLine(e.ReasonCode);
}
}
and now the code to wire up the topshelf service to the interface/concrete above
Everything below is "typical" topshelf setup.... except for 2 lines which I marked as
/* THIS IS MAGIC LINE */
Those are what get the SessionChanged method to fire.
I tested this with windows 10 x64. I locked and unlocked my machine and I got the desired result.
IMyServiceContract myServiceObject = new MyService(); /* container.Resolve<IMyServiceContract>(); */
HostFactory.Run(x =>
{
x.Service<IMyServiceContract>(s =>
{
s.ConstructUsing(name => myServiceObject);
s.WhenStarted(sw => sw.Start());
s.WhenStopped(sw => sw.Stop());
s.WhenSessionChanged((csm, hc, chg) => csm.SessionChanged(chg)); /* THIS IS MAGIC LINE */
});
x.EnableSessionChanged(); /* THIS IS MAGIC LINE */
/* use command line variables for the below commented out properties */
/*
x.RunAsLocalService();
x.SetDescription("My Description");
x.SetDisplayName("My Display Name");
x.SetServiceName("My Service Name");
x.SetInstanceName("My Instance");
*/
x.StartManually(); // Start the service manually. This allows the identity to be tweaked before the service actually starts
/* the below map to the "Recover" tab on the properties of the Windows Service in Control Panel */
x.EnableServiceRecovery(r =>
{
r.OnCrashOnly();
r.RestartService(1); ////first
r.RestartService(1); ////second
r.RestartService(1); ////subsequents
r.SetResetPeriod(0);
});
x.DependsOnEventLog(); // Windows Event Log
x.UseLog4Net();
x.EnableShutdown();
x.OnException(ex =>
{
/* Log the exception */
/* not seen, I have a log4net logger here */
});
});
My packages.config to provide hints about versions:
<package id="log4net" version="2.0.5" targetFramework="net45" />
<package id="Topshelf" version="4.0.3" targetFramework="net461" />
<package id="Topshelf.Log4Net" version="4.0.3" targetFramework="net461" />
In Windows Task Scheduler, you could create tasks that trigger on workstation lock and on workstation unlock. Each task could write a flag and timestamp to a file to state if the workstation is locked or unlocked and when it happened.
I realize that this is not a programmatic way. It is simpler than writing a service. It won't miss an event because your program happens to not be running at the time of lock/unlock transition.
Below is the 100% working code to find if the PC is locked or not.
Before using this use the namespace System.Runtime.InteropServices.
[DllImport("user32", EntryPoint = "OpenDesktopA", CharSet = CharSet.Ansi,SetLastError = true, ExactSpelling = true)]
private static extern Int32 OpenDesktop(string lpszDesktop, Int32 dwFlags, bool fInherit, Int32 dwDesiredAccess);
[DllImport("user32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern Int32 CloseDesktop(Int32 hDesktop);
[DllImport("user32", CharSet = CharSet.Ansi,SetLastError = true,ExactSpelling = true)]
private static extern Int32 SwitchDesktop(Int32 hDesktop);
public static bool IsWorkstationLocked()
{
const int DESKTOP_SWITCHDESKTOP = 256;
int hwnd = -1;
int rtn = -1;
hwnd = OpenDesktop("Default", 0, false, DESKTOP_SWITCHDESKTOP);
if (hwnd != 0)
{
rtn = SwitchDesktop(hwnd);
if (rtn == 0)
{
// Locked
CloseDesktop(hwnd);
return true;
}
else
{
// Not locked
CloseDesktop(hwnd);
}
}
else
{
// Error: "Could not access the desktop..."
}
return false;
}

Categories