Winlogon desktop again - fun stuff
Written by Wojciech Kocjan   
This article is a follow-up to Programming the Winlogon desktop article about doing Tk apps on Windows 2000/XP's desktop. This article shows how to create an application that can severely punish those who forget to lock their computers and leave them, usually this is a good joke for other people in your company.

The idea is simple - build an executable that when launched will lock your computer, but instead of showing the default window to unlock computer, show some message that you need to improve your knowledge of security. Pretty easy, isn't it? With Tk, even easier than you'd imagine. To make things easier, we'll just show a fullscreen picture - but with Tk/tile, you can make the GUI more complex, even require someone to complete a challenge in order to unlock it. Here's a list of things we need to get to achieve this: And now here's what we'll need to do:
  • Set up project directory - create Tabsfile for building, create source directory, copy twapi library and some more minor things; I'll skip details since complete ZIP as available at the end of this article
  • Create main script that either installs the application as a service if not currently running as LocalSystem or runs the actual script if running as LocalSystem user
  • Create script for actual service registration - taken from Programming the Winlogon desktop article
  • If running as a service, create a thread that will use Winlogon desktop and lock user's computer; hide actual username/password window; after user typing in stopthis, on their keyboard, application will clean up and exit and username/password window will be shown again
Pretty easy, isn't it? Now let's get to work! Assuming our sources will be in winlogonfun.vfs, we need to start by creating main.tcl there that will do determine which file to load based on whether this is a service or not:

Contents of main.tcl
package require starkit
starkit::startup
 
namespace eval wl {}
 
package require twapi
 
set wl::servicename "Winlogonfun"
set wl::binaryname [file tail [info nameofexecutable]]
 
catch {wm withdraw .}
set wl::user [::twapi::get_current_user]
 
source [file join $starkit::topdir common.tcl]
 
# check if running as SYSTEM user (which means it's running as service)
if {(${wl::user} == "LocalSystem") || (${wl::user} == "SYSTEM")} {
    if {[lindex $argv 0] == "delete"} {
  # if we're a service and command line "delete" was passed,
  # delete service and exit
  wl::deleteService
  exit 0
    }  else  {
  # otherwise run the default service functionality
  source [file join $starkit::topdir service.tcl]
    }
}  else  {
    # install as a service
    source [file join $starkit::topdir install.tcl]
}
 

File common.tcl contains some useful functions for creating/deleting a service, stopping all other binaries with the same name etc. The main functions are:

Part of common.tcl
proc wl::deleteService {} {
    variable servicename
    variable binaryname
 
    catch {
        twapi::stop_service $servicename
    }
    after 2000
    killAllProcesses "*${binaryname}*"
    after 2000
 
    catch {
        twapi::delete_service $servicename
    }
}
 
proc wl::installService {directory} {
    variable servicename
    variable binaryname
 
    twapi::create_service $servicename [file nativename [file join $directory srvany.exe]] \
        -interactive 1 \
        -starttype demand_start
 
    registry set "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\$servicename\\Parameters" Application \
        [file nativename [file join $directory $binaryname]]
    registry set "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\$servicename\\Parameters" AppParameters \
        "service"
 
}
 

When this binary is run as normal user, install.tcl is invoked, which copies the binary and srvany.exe to system folder and registers it as a binary. The code is quite simple:

Contents of install.tcl
wl::findTemp
 
wl::deleteService
 
file copy -force [file join $starkit::topdir bin srvany.exe] \
    [file join $wl::tempdirectory srvany.exe]
 
# unmount this executable's VFS to allow copying it
catch {vfs::mk4::Unmount exe [info nameofexecutable]}
 
file copy -force [info nameofexecutable] \
    [file join $wl::tempdirectory $wl::binaryname]
 
wl::installService $wl::tempdirectory
 
twapi::start_service $wl::servicename
exit 0
 
 

When this is restarted from a different directory and as a service, service.tcl creates a thread and initializes it. It also creates a command called wldone that will get back to the main thread and perform cleanup.

Contents of service.tcl
package require Thread
package require registry
 
proc wl::initThread {} {
    variable tid
 
    set tid [thread::create -desktop Winlogon]
    thread::send $tid "proc wldone {} {thread::send -async [thread::id] wl::done}"
    thread::send $tid [list source [file join $starkit::topdir service-thread.tcl]]
}
 
proc wl::initService {} {
    if {[catch {
  initThread
    }]} {
  exit 0
    }  else  {
  twapi::lock_workstation
    }
}
 
proc wl::done {} {
    exec [info nameofexecutable] delete &
    exit 0
}
 
after 1000 wl::initService
 

The new thread is initialized using service-thread.tcl file. If loading this file works, user's desktop is locked. It loads the actual image, shows it and hides the username/password window. After user types in the magic keyword (stopthis), the application cleans itself up and restores the password window:

Contents of service-thread.tcl
load {} Registry
package require Tk
package require twapi
package require tkpng
 
proc hidepasswordwindow {} {
    global passwinhwnd
    set passwinhwnd [twapi::find_windows -toplevel 1 -visible 1 -single]
    twapi::hide_window $passwinhwnd
}
proc showpasswordwindow {} {
    global passwinhwnd
    twapi::show_window $passwinhwnd
}
 
wm withdraw .
after 1000 {hidepasswordwindow ; wm deiconify .}
 
image create photo img -file [file join [file dirname [info script]] image.png]
label .img -background black -image img
pack .img -fill both -expand 1
 
wm overrideredirect . 1
wm geometry . [winfo screenwidth .]x[winfo screenheight .]+0+0
wm protocol . WM_DELETE_WINDOW {# do nothing}

bind all "stopthis" {showpasswordwindow ; wldone}
 

All of this seems pretty easy, doesn't it? All sample code above has some of the comments extracted from it to make the text readable. Full source code along with most thigns needed to build the executable is available from here.

I will try to write more things about Winlogon and more interesting usage for it. One of the things I can imagine is showing status while your computer is locked - so that you know whether a build or test has finished without constantly unlocking your computer. Stay tuned!