Click here to Skip to main content
Click here to Skip to main content

A Unix-Type Shutdown Tool for Microsoft Windows: XLogoff

, 22 May 2006
Rate this:
Please Sign up or sign in to vote.
This article presents a Unix-type shutdown tool for Microsoft Windows.

Sample Image - xlogoff.gif

1. Introduction

The logout option window on Unix-like Operating Systems is quite useful. It allows the user to choose whether to automatically launch the currently opened applications at next login. Unfortunately, Windows platforms do not have that option. It is desirable to have a tool to perform the same function for Windows users. The basic idea is that the tool should allow the user to save a Windows login session before logoff, shutdown, or restart. In this context, a session is vaguely defined as the state of running applications for the current user. An application can be either window-based or console-based (DOS type). All the saved window-based and console-based applications shall be opened automatically at next login. In summary, the tool we like should meet the following requirements:

  1. It handles all logout-related tasks, shutdown, restart, and logoff.
  2. The user can choose to keep the previous session, save the current session, or clear any session for the next login.

I have searched Google and haven't found any free tools of my interest. I decided to write one myself, and here it is. The tool was named "XLogoff", and was developed with VC++ 2003 on Microsoft .NET Framework 1.1. In order to use the source code, you should have the Windows Management Instrumentation (WMI) SDK installed.

The image above shows one login state that has been restored by the saved session data from the previous logoff.

2. Top-Level Design

The "XLogoff" tool is a service-type application, with a Windows service running on the background and a GUI for the user to interact with the service. The entire application consists of three components:

  1. XLogoff: A Windows service which starts the GUI and performs all the logout tasks, as directed by the user. The service starts a TCP/IP server on a separate thread, which listens to and consumes the GUI connections.
  2. XLGUI: The GUI window which allows the user to interact with the "XLogoff" service. The GUI sends user commands to the service via a TCP/IP client.
  3. CPAU: A utility class referenced by the "XLogoff" service. This third-party source code was written in C# and posted by "www.sayala.com". The original code works well, and thus is retained as is. No attempt was made to convert it to Managed C++, thanks to the multi-language compilation feature of VS2003.

3. User Instructions

One installation step is required to set up the tool. After you build the tool, copy all the files in the "bin/Release" folder except "User Manual.doc" to your "System32" folder. Or, you may use the Windows Installer project I provided to install/uninstall the required files to/from your computer.

3.1 To install and start "XLogoff" services:

  1. Open a command prompt window and go to your "system32" folder.
  2. Run the "XLogoff.exe -Install" command. Open the Services Control window and you should see "XLogoff Service" is installed.
  3. Right-click on "XLogoff Service" and select "Properties"; "XLogoff Service Properties" window pops up.
  4. On the Properties window:
    • Select "Automatic" to the "Startup Type" dropdown combo.
    • Change to "Log On" tab and check both "Local System account" and "Allow service to interact with desktop".
  5. Restart your computer.

XLogoff Icon

3.2 To use "XLogoff" services:

  1. Double click the "XLogoff" icon on the systray; the "XLGUI" window will pop up.
  2. On the "XLGUI" window, there are three logout options:
    • Keep previous session: leave the session as is.
    • Save current session: save the current session as a new one.
    • Don't save/keep any session: clear out all session data.

The four buttons are self-explanatory, except that the logout option you selected is performed on the session data immediately, even if you logout later.

You may also right-click on the systray icon to access three functions quickly: Logoff, Shutdown, and Restart. When you do so, you assume to keep the previous session data.

3.3 To stop "XLogoff" services:

  1. Open a command prompt window and go to the "system32" folder.
  2. Run the "XLogoff.exe -Install /u" command.

4. Use of the Source Code

First, let's look at the service class "XLogoffWinService". When the service starts, it does two things:

  1. Starts the server on a separate thread. We'll discuss this later.
  2. Starts a timer. The timed-event callback function is as follows:
private: static void OnTimedEvent(System::Object* source, 
                     System::Timers::ElapsedEventArgs* e)
{
    XLogoffThread::mut->WaitOne();

    // Use WMI to check if it's been logged out. 

    // If it is logged out, the explorer's owner should be System.

        
    ConnectionOptions *co = new ConnectionOptions();
    ManagementScope *ms = new ManagementScope("\\\\localhost", co);      

    // Query all Win32_process type management objects

    ObjectQuery *oq = new ObjectQuery("select * from Win32_Process");
    ManagementObjectSearcher *mos = new ManagementObjectSearcher(ms,oq);
    ManagementObjectCollection *moc = mos->Get();  
    ManagementObjectCollection::ManagementObjectEnumerator* moe = 
                                moc->GetEnumerator();
        
    moe->Reset();

    while(moe->MoveNext())
    {
        ManagementObject *mo = 
          dynamic_cast<MANAGEMENTOBJECT*>(moe->get_Current());

        String *str[] = {"", ""};
        str[0] = dynamic_cast<STRING*>(mo->get_Item("Name"));
            
        // Determine if System is the owner of explorer

        if(str[0]->Equals("explorer.exe"))
        {
            mo->InvokeMethod("GetOwner",(Object*[])str);

            // If it is logged in and GUI isn't alive, start GUI.

            if(!str[0]->Equals("SYSTEM") && !str[0]->Equals("System"))
            {
                CheckGUI(true);
            }

            break;
        }
    }

    XLogoffThread::mut->ReleaseMutex();
}

private:static void CheckGUI(bool add)
{
    // Take a snapshot of all running processes of XLGUI

    Process* proc[] = Process::GetProcessesByName(S"XLGUI");
    const int length = proc->Length;

    // Check if we need to start or terminate GUI

    if( add )
    {
        if( length <= 0 )
        {
            Process::Start(S"XLGUI.exe");
        }
    } else
    {
        for ( int i = 0; i < length; i++ )
        {
            proc[i]->Kill();
        }
    }
}

What this function does is ensure that the GUI is up and running. The GUI should be active after the service starts and before it stops. To achieve this goal:

  1. It checks whether a user has logged in. It invokes the GetOwner() method via Windows Management Instrumentation (WMI) and checks if the owner of Explorer is "System".
  2. If a user has logged in, it checks if the GUI is alive, and restarts it if it isn't.

Note that I have employed a mutex to ensure that the GUI activation is thread-safe and only one instance is activated.

Now, let's review the server class named "XLogoffThread". It provides the following methods:

Method Name

Description

ThreadProc()

The thread function that listens to and consumes user connections through XLGUI. All the user commands are delegated to ProcessComd().

ProcessCmd(String *msg)

Consumes a user command.

SaveBaseline()

Saves the clean login as the baseline session.

SavePrevSession()

Buffers the previous session.

KeepSession()

Saves the previous session.

ClearSession()

Cleans the session data.

SaveSession()

Saves the current session.

RestoreSession()

Restores from the saved session data.

ProcessLogoff()

Processes logoff.

ProcessShutdown()

Processes shutdown

ProcessRestart()

Processes restart.

KillRestoredProcesses()

Kills all the restored applications before logoff.

The code snippet of SaveSession() is listed as follows:

private: static void SaveSession()
{
    if( !baseline ) return;

    try
    {
        // Get all processes running on the local computer.

        Process* allProcs[] = Process::GetProcesses();
        int length = allProcs->Length;

        if( StreamWriter* w = new StreamWriter(DATA_FILE))
        {
            for( int i = 0; i < length; i++ )
            {
                // Get process information

                Process* proc = allProcs[i];
                String* procName = proc->ProcessName;
                
                if( baseline->Contains( procName ) ) continue;
                        
                String* procPath = proc->MainModule->FileName;                    
                w->WriteLine(procName);
                w->WriteLine(procPath);
            }
            w->Close();
        }
    } catch (Exception* except) 
    {
        MessageBox::Show( except->get_Message(), "XLogoff Save Error" );
    }
}

As we mentioned earlier, "baseline" saves all the processes before the logged in user or this tool has launched or restored any applications, which we used as the baseline. When saving a session, we simply open the data file, take a snapshot of the current state of the PC, and then write those processes not existing in the baseline to the data file.

Let's look at the source of RestoreSession(), which is listed below. Again, we use the baseline to ensure that we don't start any system processes. We ask the instance of the "ProcessAsUser" object "cpau" to launch all processes. This method, in turn, invokes a system call CreateProcessAsUser() to perform the job. The primary difference between this call and Process::Start() is that the former spawns a process on behalf of the currently logged in user, whereas the latter starts processes on behalf of System. Nevertheless, Process::Start() is a handy and light-weight call, which can do a lot when combined with the start option settings. I use it in CheckGUI() to start the GUI process.

private: static void RestoreSession()
{
    if ( !File::Exists(DATA_FILE) || !baseline ) return;

    try
    {
        // Read the saved session data

        StreamReader* r = new StreamReader(DATA_FILE);
        if( r )
        {
            while ( r->Peek() >= 0 )
            {
                String* tbdName = r->ReadLine();
                String* path = r->ReadLine();

                // We don't start any system processes.

                if( !baseline->Contains( tbdName ) )
                {
                    if( !cpau->Launch( path ) )
                    {
                        MessageBox::Show( path, "XLogoff Restore Error" );
                    }
                }
            }
            r->Close();
        }
    } catch (Exception* except) 
    {
        MessageBox::Show( except->get_Message(), "XLogoff Restore Error" );
    }
}

5. Points of Interest

The idea for this tool is quite straightforward, but the implementation turned out to be more complicated than I'd first thought. I'd like to elaborate some aspects here.

  1. Avoiding system processes. Earlier drafts of this tool had saved all processes, including system services. Then, I ran into the trouble that the tool occasionally crashed when logging off or logging in. The crashes were random. After more tests, it was determined that the access to the module name of a system process sometimes causes violation. To overcome this problem, I decided to avoid system processes, which is how I came up with baseline processes. The introduction of baseline data has made the tool more robust, and meanwhile saved CPU significantly.
  2. Handling the logoff session. While shutdown and restart processing is fairly simple, handling the logoff properly is a little tricky. There were occasions when not all the user processes were closed after log off. This created problems for the next login. To properly log off, I have added KillRestoredProcesses() to clean up all the user created processes before logging off.
  3. Setting process ownership. When I finished implementing logoff logic and gave a shot, everything worked well, except that all the restored processes have "System" as the owner. This is not acceptable due to numerous reasons, one of which is system security. After some researches, it was determined that CreateProcessAsUser() is a good call to use, which is where I found the CPAU C# code helpful.

6. Future Work

As you may have noticed, the tool imposes one limitation that the current version cannot save/restore any Explorer windows that may have existed before logoff. The main difficulty is that a single Explorer process supports multiple windows. It is worth investigating how to save and restore those Explorer windows, which I leave for future updates.

A second area to improve is that we may save each window's state (position, geometry, z-order, etc.) in the session data so that at next login, Windows can restore all windows to the state of the previous login.

Last, but not least, this tool can be expanded to support remote management architecture, thanks to the TCP/IP client/server for service-GUI communications. The basic idea is that you may run "XLogoff" service on each remote computer and run a "XLGUI" window on your local computer. For example, you may, through the GUI window, command each remote computer to save a current session and then shutdown.

8. Conclusion

In this article, I have presented a Unix-type shutdown tool for Microsoft Windows. With this tool, the user can save the current login session by saving all the user created processes to a data file. While the tool can do what it was expected to do, there are a number of areas where it can be improved. Future updates shall incorporate these improvements one by one.

7. History

  • 21 Feb 2006: First revision of the article and source code.
  • 22 May 2006: Fixed weird fonts.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Jun Du
Architect GuestLogix Inc.
Canada Canada
Jun is an experienced software architect. He wrote his first computer code on the tape machine for a "super computer". The tape machine reads holes on the black pape tape as source code. When manually fixing code, you need a punch and tranparent tape. To delete code, you block holes or cut off a segment and glue two ends together. To change code, you block old holes and punch new holes. You already know how to add new code, don't you? Anyway, that was his programming story in early 1980's.
 
Jun completed university with the specialty in oceanography, and graduate study in meteorology. He obtained his Ph.D. in physics. Jun has worked in a number of different areas. Since mid-90's, he has been working as a software professional in both military & commercial industries, including Visual Defence, Atlantis Systems International and Array Systems Computing.
 
Currently, Jun is an architect at GuestLogix, the global leader in providing onboard retail solutions for airlines and other travel industries. He is also the founder of Intribute Dynamics, a consulting firm specialized in software development. He has a personal blog site, although he is hardly able to keep it up to date.
 
In his spare time, Jun loves classic music, table tennis, and NBA games. During the summer, he enjoyes camping out to the north and fishing on wild lakes.

Comments and Discussions

 
GeneralAccidental re-posting... nothing new PinmemberJun Du23-May-06 3:11 
GeneralPromising but looking for a little bit more PinmemberDavid I10-Apr-06 10:27 
GeneralRe: Promising but looking for a little bit more PinmemberJun Du10-Apr-06 13:44 
GeneralRe: Promising but looking for a little bit more PinmemberPedroMC31-May-06 0:12 
Generalnice PinmemberVarindir Rajesh Mahdihar3-Mar-06 5:13 
GeneralRe: nice PinmemberJun Du3-Mar-06 7:06 
Generalgreat job. Pinmembermargiex26-Feb-06 20:19 
GeneralRe: great job. PinmemberJun Du27-Feb-06 4:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 22 May 2006
Article Copyright 2006 by Jun Du
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid