CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 11 of 11

Hybrid View

  1. #1
    Join Date
    Nov 2003
    Location
    Portland, OR
    Posts
    894

    How to logoff a user when the workstation is locked?

    I wrote a Windows application that comes with two modules: service and user-mode exes. The service implements its own scheduler and may log-off a user at a predefined time. For that I was using the following call that is triggered from my user-mode module exe in a logged-on user session that has to be logged off:

    Code:
    BOOL result = ExitWindowsEx(EWX_LOGOFF, reason);
    This works fine, except of the situation when a user's account is locked. In that case that API doesn't seem to do anything at all even through I get 1 returned from it.

    So I was curious, is there any other way to log off a user when their account is locked? (One condition I have in this case is that if that user had any unsaved documents then the log-off should not be forced.)

  2. #2
    2kaud's Avatar
    2kaud is offline Super Moderator Power Poster
    Join Date
    Dec 2012
    Location
    England
    Posts
    7,822

    Re: How to logoff a user when the workstation is locked?

    As far as I am aware, there is no way for one program to know if other programs have unsaved open files (although you can find out which files are open). It is up to the individual programs what to do when they receive WM_CLOSE, WM_QUERYENDSESSION etc.

    Re forcing a logoff when a user's account is locked. See http://www.vistaheads.com/forums/mic...on-locked.html and others. They basically say you can't via a program.
    All advice is offered in good faith only. All my code is tested (unless stated explicitly otherwise) with the latest version of Microsoft Visual Studio (using the supported features of the latest standard) and is offered as examples only - not as production quality. I cannot offer advice regarding any other c/c++ compiler/IDE or incompatibilities with VS. You are ultimately responsible for the effects of your programs and the integrity of the machines they run on. Anything I post, code snippets, advice, etc is licensed as Public Domain https://creativecommons.org/publicdomain/zero/1.0/ and can be used without reference or acknowledgement. Also note that I only provide advice and guidance via the forums - and not via private messages!

    C++23 Compiler: Microsoft VS2022 (17.6.5)

  3. #3
    Arjay's Avatar
    Arjay is offline Moderator / EX MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    13,490

    Re: How to logoff a user when the workstation is locked?

    Years ago, I solved a similar problem within a distributed test framework. We automatically reimaged test machines and ran tests on 80 lab machines. Each machine had a test agent client that would check if errors appeared, restart the machine, reimage and so on. The approach that I took was to track initial processes on startup (and flag these as base processes) and then while running tests, I would update the process list every two seconds. What is applicable here is how I would close down these processes in the event of a failure. What I did was to attempt to 'politely' close the process using WM_QUERYENDSESSION (as 2kaud mentioned). In order to do this, you need to retrieve the top level window of each process and sendmessagetimeout the message to it. That will tell you if you the process can be shutdown (or if the user can be logged off, in your case). For shutting the system down, I preferred to use InitiateSystemShutdown over ExitWindowsEx, because back then, ExitWindowsEx wasn't as reliable.

    Here is the close process code for an individual process:

    Code:
    //+-----------------------------------------------------------------------
    //	Public Method:  RemoveProcess
    //
    //	Purpose:		Attempts to 'politely' tell the process to
    //                  terminate itself.  If process still exists
    //                  after gentle prodding, use TerminateProcess
    //                  to remove the process.  
    //
    //	Restrictions:	none
    //
    //	Parameters:		void
    //
    //	Return:			void
    //------------------------------------------------------------------------
    void CProcItem::RemoveProcess()
    {
        BOOL bDisplayError  = FALSE;
        HANDLE hProcess     = NULL;
        DWORD dwExitCode    = 0;
        DWORD dwResult      = 0;
    
        // Don't want to kill if base process
        if( m_bIsBaseProcess )
        {
            return;
        }
    
        if((hProcess = OpenProcess(PROCESS_ALL_ACCESS | SYNCHRONIZE,
                                    FALSE, 
                                    (DWORD)m_hPId)) != NULL)
        {
            // For processes with main level hWnds,
            // attempt to terminate nicely
            if(NULL != m_hWnd)
            {
                if(SendMessageTimeout(m_hWnd,
                                    WM_QUERYENDSESSION,
                                    0,
                                    ENDSESSION_LOGOFF,
                                    SMTO_ABORTIFHUNG,
                                    2000,       
                                    &dwResult))
                {
                    // Process will terminate nicely, so tell it to close
                    SendMessageTimeout(m_hWnd, 
                                        WM_CLOSE,
                                        TRUE,
                                        0,
                                        SMTO_ABORTIFHUNG,
                                        2000,   
                                        &dwResult);
                }
            }
            else
            {
                // TODO: Find a polite way to terminate 
                // processes without hWnds, i.e., console apps, etc.)
            }
    
            // Give time for the process to go away
            switch(WaitForSingleObject(hProcess, 2000)) // Wait 2 seconds
            {
            case WAIT_OBJECT_0:
                // Process terminated on its own
                break;
            default:
                //WAIT_ABANDONED, WAIT_FAILED, or WAIT_TIMEOUT
                // Process still exists, time to get rough
    
                if(GetProcessInfo()->IsNT())
                {
                    // Special-case killing WOW tasks: kill only ntvdm which in turn will
                    // will kill the child wow processes.
                    //This method helps to avoid the "can't terminate wow process 
                    //because wowexec is not letting it run" error dialogs
                    if (IsWowTask() && _wcsicmp(m_sImageName.c_str(), L"ntvdm.exe"))
                    {
                        ATLASSERT(GetProcessInfo()->m_pfn_VdmTerminateTaskWow);
                        GetProcessInfo()->m_pfn_VdmTerminateTaskWow(GetRealPID(), m_hTaskWow);
                    }
                }
                
                if(GetExitCodeProcess(hProcess, &dwExitCode))
                {
                    if(STILL_ACTIVE == dwExitCode)
                    {
    					USES_CONVERSION;
    					ATLTRACE( _T("IProcess::TerminateProcess(): %s\r\n"), W2T(m_sImageName.c_str()) );
    
                        if (!TerminateProcess(hProcess,0)) 
                        {
                            bDisplayError = TRUE;
                        }
                    }
                }
            }
           
            CloseHandle( hProcess );
        }
        // Invalidate so it's removed from the list
        Invalidate();
    
    }
    You can ignore the ntvdm code as it was a special case for handling 16-bit wow processes.
    Last edited by Arjay; December 1st, 2013 at 05:16 PM.

  4. #4
    2kaud's Avatar
    2kaud is offline Super Moderator Power Poster
    Join Date
    Dec 2012
    Location
    England
    Posts
    7,822

    Re: How to logoff a user when the workstation is locked?

    Consoles handle logoff/shutdown queries via CTRL_SHUTDOWN_EVENT or CTRL_LOGOFF_EVENT notifications which should be processed in the console's handler routine (if the console program is well behaved!).

    The close process code will enable processes to be checked to see if they can be closed (assuming the programs process the WM_QUERYENDSESSION message - I found some that don't), and to forceibly close them. However, this doesn't seem to address the OP's question re logging off a user when the workstation is locked. The information I've found about this says you can't do it programatically.

    If ExitWindowsEx returns true, this doesn't mean that the operation has been carried out. All it means is that the command is valid and has been 'passed on' to the OS to perform. Whether the operation itself has been successful or not can't be determined from this function.
    All advice is offered in good faith only. All my code is tested (unless stated explicitly otherwise) with the latest version of Microsoft Visual Studio (using the supported features of the latest standard) and is offered as examples only - not as production quality. I cannot offer advice regarding any other c/c++ compiler/IDE or incompatibilities with VS. You are ultimately responsible for the effects of your programs and the integrity of the machines they run on. Anything I post, code snippets, advice, etc is licensed as Public Domain https://creativecommons.org/publicdomain/zero/1.0/ and can be used without reference or acknowledgement. Also note that I only provide advice and guidance via the forums - and not via private messages!

    C++23 Compiler: Microsoft VS2022 (17.6.5)

  5. #5
    Join Date
    Nov 2003
    Location
    Portland, OR
    Posts
    894

    Re: How to logoff a user when the workstation is locked?

    Thank you, guys. I see Arjay's point, if I know that all processes don't have unsaved data I can use a forced log-off. Unfortunately as 2kaud pointed out this process is not conclusive. I've witnessed myself that WM_QUERYENDSESSION was not honored and in that case I can't risk logging off a user and lose their unsaved data.

    @Arjay I looked at alternative methods to log off a user and don't think InitiateSystemShutdown will work for me because it initiates shutdown/reboot, which I don't need. There's also WTSLogoffSession which unfortunately works as if I specified the EWX_FORCE flag.

    As an alternate method I was curious if there's a way to unlock the workstation, maybe even undocumented?

    Also since this LockWorkStation function seems to be of a relevance in case of a log-off, I'm not entirely clear how it works. Say, I have 3 interactive user accounts on the workstation with 3 of my user-mode processes running. Say, one calls LockWorkStation, then another one calls LockWorkStation and then after some time the 3rd process also calls LockWorkStation. Will this make each user account to be locked? Or will it toggle the lock between user 1, then user 2 and eventually user 3? If the latter case is true, maybe I can "juggle" this LockWorkStation call between user sessions and "trick" the ExitWindowsEx API this way. Just curious what you guys think on this?

  6. #6
    Join Date
    Nov 2000
    Location
    Voronezh, Russia
    Posts
    6,620

    Re: How to logoff a user when the workstation is locked?

    Quote Originally Posted by dc_2000 View Post
    Also since this LockWorkStation function seems to be of a relevance in case of a log-off, I'm not entirely clear how it works. Say, I have 3 interactive user accounts on the workstation with 3 of my user-mode processes running. Say, one calls LockWorkStation, then another one calls LockWorkStation and then after some time the 3rd process also calls LockWorkStation. Will this make each user account to be locked? Or will it toggle the lock between user 1, then user 2 and eventually user 3? If the latter case is true, maybe I can "juggle" this LockWorkStation call between user sessions and "trick" the ExitWindowsEx API this way. Just curious what you guys think on this?
    The API article states that the function is about locking interactive session's display. In terms of WTS it must be about locking WTS session's virtual display. So by no means it's about juggling sessions, but it's literally about locking session's input/output. (Not sure what did you mean by "locking user account" though)

    In case you really need to reliably log off users, you should find a way to disable the lock-workstation function and make your users to log off explicitly instead. Look in direction of system policies.


    As an alternate method I was curious if there's a way to unlock the workstation, maybe even undocumented?
    I'd like to warn you about going to enter an unsafe ground. Official security mechanisms is way too sensitive matter for being recklessly breached, or exploiting a breach, just for providing a questionable benefit/feature. And no doubt locking workstation is a part of Windows security.
    Best regards,
    Igor

  7. #7
    Arjay's Avatar
    Arjay is offline Moderator / EX MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    13,490

    Re: How to logoff a user when the workstation is locked?

    Yeah, InitiateSystemShutdown won't work. I got off track after posting all the code.. Doing some research, I see that you can prevent a user from locking the workspace by changing a group policy (users only can logoff). Will that work for you?

    As far as the lock toggle question? Use the WTSRegisterSessionNotification to register for the session lock message and test for it in each of the scenarios.

    Btw, I haven't witnessed apps ignoring the WM_QUERYENDSESSION message, but have noticed that apps that are hung don't respond to it (like, duh?). SendMessageTimeout will return a 0 and GetLastError returns ERROR_TIMEOUT in this case.
    Last edited by Arjay; December 2nd, 2013 at 01:47 AM.

  8. #8
    2kaud's Avatar
    2kaud is offline Super Moderator Power Poster
    Join Date
    Dec 2012
    Location
    England
    Posts
    7,822

    Re: How to logoff a user when the workstation is locked?

    As an alternate method I was curious if there's a way to unlock the workstation, maybe even undocumented?
    No documented method - and as Ignor correctly points out in post #7, stay well clear of undocumented ways.

    If you find a workstation locked, rather than logoff what about reboot?

    Preventing users from locking the workstation when they temporarily leave the computer is not good security practice Yes, they can logoff - but then they have to save work etc first before they logoff and have to log back on again when they come back and reload the applications they had open etc etc. This amounts to a lot of time waste - especially if logon/logoff is slow if using roaming profiles. The users are unlikely to do it properly.

    Why do you want to logoff a user at a pre-defined time? Have you thought about using logon hours in the users profile to restrict usage?
    All advice is offered in good faith only. All my code is tested (unless stated explicitly otherwise) with the latest version of Microsoft Visual Studio (using the supported features of the latest standard) and is offered as examples only - not as production quality. I cannot offer advice regarding any other c/c++ compiler/IDE or incompatibilities with VS. You are ultimately responsible for the effects of your programs and the integrity of the machines they run on. Anything I post, code snippets, advice, etc is licensed as Public Domain https://creativecommons.org/publicdomain/zero/1.0/ and can be used without reference or acknowledgement. Also note that I only provide advice and guidance via the forums - and not via private messages!

    C++23 Compiler: Microsoft VS2022 (17.6.5)

  9. #9
    2kaud's Avatar
    2kaud is offline Super Moderator Power Poster
    Join Date
    Dec 2012
    Location
    England
    Posts
    7,822

    Re: How to logoff a user when the workstation is locked?

    Have a look at pshutdown command from SysInternanls
    http://technet.microsoft.com/en-us/s...rnals/bb897541
    particularly the -o option.

    Also have a look at
    http://www.intelliadmin.com/?p=4439
    All advice is offered in good faith only. All my code is tested (unless stated explicitly otherwise) with the latest version of Microsoft Visual Studio (using the supported features of the latest standard) and is offered as examples only - not as production quality. I cannot offer advice regarding any other c/c++ compiler/IDE or incompatibilities with VS. You are ultimately responsible for the effects of your programs and the integrity of the machines they run on. Anything I post, code snippets, advice, etc is licensed as Public Domain https://creativecommons.org/publicdomain/zero/1.0/ and can be used without reference or acknowledgement. Also note that I only provide advice and guidance via the forums - and not via private messages!

    C++23 Compiler: Microsoft VS2022 (17.6.5)

  10. #10
    Arjay's Avatar
    Arjay is offline Moderator / EX MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    13,490

    Re: How to logoff a user when the workstation is locked?

    Good idea 2kaud. Similarly you can try the built-in shutdown.exe utility (see the /l switch).

    C:\>shutdown /?
    Usage: shutdown [/i | /l | /s | /r | /g | /a | /p | /h | /e] [/f]
    [/m \\computer][/t xxx][/d [p|u:]xx:yy [/c "comment"]]

    No args Display help. This is the same as typing /?.
    /? Display help. This is the same as not typing any options.
    /i Display the graphical user interface (GUI).
    This must be the first option.
    /l Log off. This cannot be used with /m or /d options.
    /s Shutdown the computer.
    /r Shutdown and restart the computer.
    /g Shutdown and restart the computer. After the system is
    rebooted, restart any registered applications.
    /a Abort a system shutdown.
    This can only be used during the time-out period.
    /p Turn off the local computer with no time-out or warning.
    Can be used with /d and /f options.
    /h Hibernate the local computer.
    Can be used with the /f option.
    /e Document the reason for an unexpected shutdown of a computer.
    /m \\computer Specify the target computer.
    /t xxx Set the time-out period before shutdown to xxx seconds.
    The valid range is 0-315360000 (10 years), with a default of 30.
    If the timeout period is greater than 0, the /f parameter is
    implied.
    /c "comment" Comment on the reason for the restart or shutdown.
    Maximum of 512 characters allowed.
    /f Force running applications to close without forewarning users.
    The /f parameter is implied when a value greater than 0 is
    specified for the /t parameter.
    /d [p|u:]xx:yy Provide the reason for the restart or shutdown.
    p indicates that the restart or shutdown is planned.
    u indicates that the reason is user defined.
    If neither p nor u is specified the restart or shutdown is
    unplanned.
    xx is the major reason number (positive integer less than 256).
    yy is the minor reason number (positive integer less than 65536).

  11. #11
    Join Date
    Nov 2003
    Location
    Portland, OR
    Posts
    894

    Re: How to logoff a user when the workstation is locked?

    I've been asked via a private message to post an update to this thread:
    I am just wondering if you ever found a solution to the thread you created? ....
    Well, yes and no. Yes, meaning that I learned to get around it. You basically have the following options:

    A. Use "forced" option, which will work just fine. One obvious "handicap" in this case, as you can imagine, is that a logged in user will lose all of their unsaved data. (Which is bad!) You may choose to show a big flashing warning saying that, "Hey, dude, if you click OK we will log you off and all your unsaved data will be lost!" It is still dangerous though, because many people click things without reading them.... So, exactly because of that, I did not choose that solution. (Btw, if you do choose it and you have a service to run your code from use WTSLogoffSession() API instead for forced user logoff.)

    B. Your second option is to track whether or not the current user session is locked and if it is not, proceed with a non-forced logoff using ExitWindowsEx() API w/o EWX_FORCE flag. But if the user session is locked, then do nothing (or log an internal error or something.) Obviously in this case there's no need to display any error messages for a user because their desktop is locked and they won't see it in time.

    The issue here is how to know that the user session is locked? Unfortunately there's no documented way to know it. (The solution that everyone recommends is to use WM_WTSSESSION_CHANGE notification, but what they fail to mention is that it is an unreliable method as you won't know the state of user session(s) when your app starts. For instance, when a user first runs your app one of the logged in interactive user sessions may be already locked.)

    So there's an undocumented approach (use at your own risk.) Note that this code must be called from an interactive user process running in a user session that you want to learn the locked status of (in other words, DON'T run it from a service directly):

    Code:
    int IsCurrentUserSessionLocked()
    {
    	//RETURN:
    	//	1 = yes
    	//	0 = no
    	//	-1 = error (check GetLastError() for info)
    	int res = -1;
    
    	HDESK hDsk = ::OpenInputDesktop(0, FALSE, DESKTOP_READOBJECTS);
    	if(hDsk)
    	{
    		//Got it, then no
    		res = 0;
    
    		::CloseDesktop(hDsk);
    	}
    	else
    	{
    		//Check last error
    		int nOSError = ::GetLastError();
    		if(nOSError == NO_ERROR ||
    			nOSError == ERROR_ACCESS_DENIED)
    		{
    			//On XP we get no error, 
    			//On Windows 7 it's access denied when the desktop is locked
    			res = 1;
    		}
    	}
    
    	return res;
    }
    I tested it to work on most Windows OS starting from XP, except when the user session is just loading up, after an interactive user login. (But it's a marginal case that you probably won't run into in most cases.)

    There's one more "nasty" aspect of calling non-forced version of ExitWindowsEx that you need to be aware of. If there's any application that prevents logoff by failing WM_QUERYENDSESSION notification, the logoff/reboot/shut-down will be aborted, and OS will show an overlay screen as such (just a stock image from Google):

    Name:  logoff.jpg
Views: 2128
Size:  33.6 KB

    This one is for Windows 7, but it differs in how it looks for each version of OS. In XP this message was displayed in a simple GUI window, starting from Vista/7 they were showing it in a full-screen UIPI-isolated window, but starting from Windows 8, they display it in a secure desktop.

    And worse of all, when that overlay is displayed, the user session will enter a "shutting down status" (sp?) in which case any user-mode process will fail to start for that user session. You can determine this state by calling:
    Code:
    ::GetSystemMetrics(SM_SHUTTINGDOWN);
    from that user session, but there's absolutely nothing that you will be able to do with it. You will definitely not be able to cancel it. That is clearly not documented and the only way that I know of canceling it is for the user to click "Cancel" button on that overlay screen. (On Windows 10 that overlay screen seems to cancel itself automatically after 1 minute.)

    C. Lastly your safest bet, is not to worry about logging off users. I don't know what your application does specifically, so maybe you can get away with just instructing users to log off or reboot with a simple popup message from your app and let them deal with it using Windows default methods instead (via the Start button.)

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured