A Programmer’s Perspective on New System DLL Features in Windows NT 5.0, Part II

by Matt Pietrek

When MSJ asked me to write about Microsoft® Windows NT® 5.0, I decided to focus on the core portions of the operating system that have changed since Microsoft Windows NT 4. Last month, I covered what’s new in the kernel, USER, and GDI components. This month, I’ll look at additions to ADVAPI32, the shell, the common controls, and COM. Although technically not core items, I’ll also look at additions to WININET.DLL and IMAGEHLP.DLL, as well as the new Shell Light Weight API and task scheduling interfaces. Before you dive in, be aware that much of this was written prior to the release of Windows NT 5.0 Beta 1. Any and all of what I describe here is subject to change.

ADVAPI32 is best known for its ownership of the registry APIs. It does a lot more than that, though. Windows NT 5.0 adds quite a few new APIs to ADVAPI32, especially in the areas of security and cryptography. I’ll mention that the only new registry function (so far) is RegOverridePredefKey. It lets you substitute one of the predefined keys with a different key. The predefined keys are the keys you see when you first start RegEdit, and include HKEY_ CLASSES_ROOT, HKEY_CURRENT_USER, and HKEY_ LOCAL_MACHINE.

The Windows NT 5.0 registry editor, RegEdit.EXE, has an awesome new feature for anybody who’s laboriously copied the name of a deeply nested registry key. Simply right-click on the desired registry key, and the context menu gives you the opportunity to Copy Key Name. This copies the key’s complete path into the clipboard, ready to paste wherever you please.

New Security APIs

There are lots of new Windows NT 5.0 security APIs. I won’t pretend I’m an expert on Windows NT security, so I’ll just give a high-level rendition of the groups of new APIs. The first set of security APIs is the AccessCheck group, which determines whether a security descriptor grants a specified set of access rights to a client.

AccessCheckByType
AccessCheckByTypeAndAuditAlarm
AccessCheckByTypeResultList
AccessCheckByTypeResultListAndAuditAlarm

Next up is the Add functions, which add new Access Control Entries (ACEs) to an Access Control List (ACL). Some of these new APIs are just extended versions of existing APIs.

AddAccessAllowedAceEx
AddAccessAllowedObjectAce
AddAccessDeniedAceEx
AddAccessDeniedObjectAce
AddAuditAccessAceEx
AddAuditAccessObjectAce

Windows NT 5.0 provides APIs for converting between ACLs (ACTRL_ACCESS) and security descriptors (SECURITY_DESCRIPTOR).

ConvertAccessToSecurityDescriptor
ConvertSecurityDescriptorToAccess
ConvertSecurityDescriptorToAccessNamed

Next up in the hit parade of new security APIs are the SecurityInfoEx APIs. These APIs differ from their non-Ex versions in that they allow you to retrieve access-control information for the properties on an object, as well as for the object itself.

GetNamedSecurityInfoEx
GetSecurityInfoEx
SetSecurityInfoEx
SetNamedSecurityInfoEx

A new API, SetEntriesInAccessList, creates a new ACL by merging new access-control information into an existing ACL. The SetEntriesInAuditList API does the same thing, but for audit lists (ACTRL_AUDIT).

The CreatePrivateObjectSecurityEx and SetPrivateObjectSecurityEx functions are extensions to existing APIs that create and modify security descriptors. The primary difference in the Ex functions is that they can specify flags that control how ACEs are inherited from the security descriptor of the parent container.

Although they sound like part of the CryptoAPI 2.0, the new EncryptFile and DecryptFile APIs belong to ADVAPI32.DLL. They do file encryption and decryption at the file system level. The encryption is implemented as a filter driver, and only works on NTFS formatted volumes. To put a file into the encrypted state, you call EncryptFile, and to decrypt it, you use DecryptFile. (Duh!)

From the user’s perspective, encryption is an attribute of a file, much like compression. It’s worth noting that a file that’s encrypted cannot be compressed, and vice versa. A file that’s encrypted by one user appears completely unchanged to the user. However, it isn’t accessible to someone logged in as a different user. To test this, I logged in as the Administrator, right-clicked on a file in a Microsoft Internet Explorer window, and selected Encrypt. I then logged out, and logged in as Guest. When I attempted to access the same file, I received the message “A device attached to the system is not functioning.” This actually makes sense, as the file system Encryption filter driver simply disallows access to the file by someone without the proper security attributes.

New Windows NT 5.0 Shell Additions

One of the top new features in Windows NT 5.0 is its support for the Active Desktop™ (which Windows® 98 also supports). The topic of Active Desktop programming is way beyond the scope of this article, so I’ll defer an in-depth discussion to some future MSJ article. I’ll go over a few of the more important new shell APIs and ActiveX™ interfaces that are defined in SHELLAPI.H and SHLOBJ.H.

On the API side of things, there’s a relative handful of new tricks. SHQueryRecycleBin tells you (in 64-bit integer format, no less!) how many items are in the recycle bin, and how much space it takes up. Information can be retrieved for a particular drive, or for all bins on all drives. To empty out a particular recycle bin (or all bins), there’s the SHEmptyRecycleBin API. I’ve put together a tiny cheesy sample, RecycleBin.EXE, the source for which can be found in Figure 1.

Figure 1: RecycleBin.cpp

//======================================================================
// Matt Pietrek
// Microsoft Systems Journal, December 1997
// FILE: RecycleBin.CPP
// To compile: CL </DUNICODE=1 /D_UNICODE=1> RecycleBin.CPP SHELL32.LIB
//======================================================================
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x500
#include <windows.h>
#include <shellapi.h>
#include <stdio.h>
#include <tchar.h>

int main()
{
    SHQUERYRBINFO shqbi = { sizeof(shqbi), -1, -1 };
    
    if ( S_OK != SHQueryRecycleBin( 0, &shqbi ) )
    {
        _tprintf( _T("Error!\n") );
        return 1;
    }
    
    _tprintf( _T("Items:%u  Bytes used:%u"), (DWORD)shqbi.i64NumItems,
             (DWORD)shqbi.i64Size );

    SHEmptyRecycleBin( 0, 0, SHERB_NOPROGRESSUI );

    return 0;
}

Other new shell APIs include SHGetNewLinkInfo, which can help you create a new link. It will return information such as whether the target is also a link—if so, it should just be copied—and what the new name should be. There’s also the SHInvokePrinterCommand, which lets you easily query the printer, install drivers for a network printer, print a test page, and get properties for a document in its print queue. The existing ShellExecute API extends the SHELLEXECUTEINFO structure to let you specify which monitor an application should be initially be displayed on. The last new shell API to mention here is DoEnvironmentSubst. You pass it a string with environment variables embedded (such as %windir%), and the API will replace them with the current environment settings.

The most interesting new shell functionality in Windows NT is the new ActiveX interfaces. The flagship interface here is IActiveDesktop. Its methods are listed below and are mostly self-explanatory.

ApplyChanges
GetWallpaper / SetWallpaper
GetWallpaperOptions / SetWallpaperOptions
GetPattern / SetPattern
GetComponentsOptions / SetComponentsOptions
AddComponent / ModifyComponent / RemoveComponent
GetComponentsCount
GetComponent / GetComponentByID
GenerateComponentHtml

You obtain an Active Desktop pointer by calling CoCreateInstance and specifying CLSID_ActiveDesktop and IID_IActiveDesktop as the CLSID and IID. Note that the GUID for IActiveDesktop changed between IE 4.0 Preview 2 and the final release of IE 4.0. This means that anyone targeting the Windows NT 5.0 interface on Windows NT 5.0 Beta 1 will need to use the old version, and any binary compiled for one release will not work with the other. The Platform SDK ships both versions, defaulting to the IE 4.0 final GUID and definitions.

To use the Windows NT 5.0 Beta 1 version, you will need to copy the mssdk\include\nt5b1 directory to mssdk\include (or put it earlier on the path) and the mssdk\lib\nt5b1 directory to mssdk\lib. (Further information is found in the Platform SDK release notes.) The Internet Client SDK (aka Internet Explorer 4.0 Toolkit) can’t be used to target Windows NT 5.0 Beta 1 since it has the later definitions.

Another interesting new ActiveX interface is IURLSearchHook. IURLSearchHook, which is called by Internet Explorer to translate URLs using unknown strings into something it can use. When attempting to browse to a URL address that does not contain a protocol, Internet Explorer first attempts to determine the correct protocol using the unmodified address. If this fails, Internet Explorer creates URL Search Hook objects that have been registered, and calls each object’s Translate method until the URL has been translated or until all hooks have been called.

New Common Controls

Ever since the Win32® common controls first appeared in Windows 95, they’ve been constantly tweaked and extended. The changes between the Windows NT 5.0 common controls and those in Windows NT 4.0 are huge, and normally you’d expect them to go through all the beta testing that a major operating system upgrade entails. The Internet wars haven’t let things play out this way, however.

In between Windows NT 4.0 and Windows NT 5.0, several releases of Internet Explorer 3.x have been released, with new common controls coming as part of the deal. As if that weren’t enough, the Internet Explorer 4.0 preview has added even more functionality to the common controls corral. In comparing the current COMMCTRL.H with the version released for Windows NT 4.0, my head spun when I saw all the differences. The newer COMMCTRL.H is more than double the size of the original.

I’m not going to even attempt to describe all the differences in COMMCTRL.H. Many changes are simply additional custom window messages (or pseudo-APIs that are really just macros around a call to SendMessage). These new messages, along with many new #defines, let you extend the functionality of the common controls beyond what was available in Windows NT 4.0 or Windows 95. Many of these additions have already been covered extensively in Strohm Armstrong’s October and November 1996 MSJ articles (“Previewing the Common Controls DLL for Microsoft Internet Explorer 4.0”). What I will briefly describe here are the additional common controls that weren’t available in the Windows NT 4.0 timeframe.

Before I get to the new controls, there is one new API that definitely requires mentioning. InitCommonControlsEx is a superset of the existing InitCommonControls API. The primary job of InitCommonControls is to register the appropriate window class for each common control. The flaw with InitCommonControls is that if you don’t want to use every common control, you waste time and space registering classes you don’t care about. InitCommonControlsEx remedies this by allowing you to select which controls to register. Simply fill in the INITCOMMONCONTROLSEX structure with the ICC_XXX flags representing the controls that you’ll use, then pass the structure to InitCommonControlsEx.

The ComboBoxEx control is an extended combo box that supports images alongside the combo box’s items. To make the images easily accessible, the control uses image lists. Image lists are one of the original common control components. In addition to the standard combo box styles, the ComboBoxEx control supports new styles such as CBES_EX_ CASESENSITIVE, which enables case sensitive searching.

The calendar control provides an easy way for the user to select a date from a control resembling a monthly calendar (see Figure 2). The left and right arrows at the top let you scroll to previous and subsequent months. Clicking on the month name at the top brings up a menu with all the months. Clicking on the year creates an up-down control that quickly moves you forwards and backwards in year increments.

Figure 2: Calendar Control

The date and time picker (DTP) control displays time information and acts as the interface through which users modify time information (see Figure 3). The control’s display consists of fields defined by the control’s format string. Each field in the control displays a portion of the time information that the control stores internally. The user can click a field to set keyboard focus, and then provide keyboard input to change the time information represented by that field.

Figure 3: DTP Control

Rebar controls act as containers for child windows. An application assigns child windows, which are often other common controls, to a rebar control band. Rebar controls contain one or more bands. Each band can have any combination of a gripper bar, a bitmap, a text label, and a child window. You’ve probably seen rebar controls in Internet Explorer 3.0 and 4.0, as well as in Visual Basic® 5.0. You may also have heard rebar controls referred to as CoolBars. One of the best parts about rebar controls is that users can move the bands and bars around to their heart’s content, practically guaranteeing that no two users will have the exact same layout in their program. Yep, a technical support staffer’s dream.

The Internet Protocol (IP) address control is similar to an edit control, but only allows you to enter numerical addresses in IP format. This format consists of four three-digit fields. Each field is treated like a separate edit control. Tab and Shift-Tab let you navigate between the fields, as shown in Figure 4. It’s possible to configure the control so that it forces the IP address fields to fall within ranges you define (for example, your company’s subnet mask).

Figure 4: IP Address Control

Flat scrollbars behave identically to normal scrollbars. The only difference is in how they are displayed to the user. Perhaps the UI team at Microsoft waxes nostalgic for those pre-Windows 3.x days, when everything in the UI was flat, rather than three dimensional. Unfortunately, the normal scrollbar APIs don’t work with flat scrollbars. Instead, you’ll need to call a new set of APIs (all prefixed with FlatSB_). It’s also necessary to call InitializeFlatSB before using any of the other FlatSB APIs.

The pager control is a parent window class that can be used to scroll any child window. It works as a viewport, avoiding the system scrollbars that require the child to build in scrolling knowledge and how to draw at specific offsets. You can see examples of pagers in the IE 4.0 channels explorer bar and the quicklinks desktop toolbar. The little left/right scrollers and top/bottom scrollers are pagers. Spy++ will also show this window hierarchy.

Finally, there’s custom draw, which isn’t a common control; it is a service that many common controls provide. Custom draw services give you greater flexibility over a control’s appearance. Your application can use custom draw notifications to change the font used to display items or manually draw an item without having to do a full-blown owner draw.

COM/OLE Additions to Windows NT 5.0

I won’t even attempt to cover everything that’s new in COM and ActiveX for Windows NT 5.0. COM+ is being covered by Mary Kirtland in this issue. But there are a few COM/OLE highlights that are worth going over.

A new concept in COM under Windows NT 5.0 is surrogates. A surrogate is a special process into which DLL servers can be loaded. This gives the DLL server the advantages of an EXE server without all the additional coding. It also allows different DLL servers to be loaded together within a single process, reducing the number of server processes needed. Among the benefits of surrogate processes is fault isolation. In other words, a buggy DLL server won’t bring down your entire program. Surrogates also allow servers to serve multiple clients simultaneously, and let the DLL server provide services to clients on other machines (à la DCOM). Of course, the disadvantages of EXE processes (such as greater execution overhead) are also present with surrogates.

To use the system-supplied surrogate, you need to make one change to the registry on the server machine: under the AppID key for your server, add “DllSurrogate” as a named value. Assuming you have a valid InprocServer32 key for this server, the system will now respond to a request for a local (EXE) server using the default surrogate. For most situations, the system-supplied surrogate should be sufficient. If you need detailed control over activation, security, or threading, you can create your own custom surrogate. For more information about custom surrogates, see the latest Platform SDK documentation.

Another new COM addition in Windows NT 5.0 is IGlobalInterfaceTable. This interface lets any apartment in a process get access to an interface implemented on an object in any other apartment within the process. The IGlobalInterfaceTable::RegisterInterfaceInGlobal method lets you register interfaces as being “global” throughout the process. IGlobalInterfaceTable::GetInterfaceFromGlobal retrieves a pointer to that interface from any other apartment through a token. Finally, IGlobalInterfaceTable::RevokeInterfaceFromGlobal revokes a previously registered global interface.

Using IGlobalInterfaceTable can be an efficient way for a process to store an interface pointer in memory that may be accessed from multiple apartments within the process—for instance, process global variables and freethreaded objects containing interface pointers to non-freethreaded objects. The IGlobalInterfaceTable capabilities are not portable across process or machine boundaries, so cannot be used as a substitute parameter passing mechanism.

New WinInet Fun with Windows NT 5.0

As Internet technologies continue to multiply at an amazing rate, Microsoft operating system gurus somehow keep up with it, adding new features and APIs. The focus point for Internet-related APIs is WININET.DLL; quite a bit has been added since WinInet first appeared for Internet Explorer 3.0, including support for HTTP 1.1 headers. I’ll just go over some of the new APIs. There are many new #defines and enumerations in WININET.H that I won’t describe because they’re for preexisting APIs.

To start out with, the WinInet folks have discovered that not everybody is continuously hooked up to the Internet via a T3 line to their desktop. A whole new set of APIs shown in Figure 5 enable you to programmatically fire up your modem to hook up to your ISP, hang up the connection, and otherwise query what sort of IP connection you have. Quick quiz: without looking, what’s the difference between InternetGoOnline, InternetDial, and InternetAutodial?

Figure 5: WinInet Modem APIs

InternetGoOnline Prompts the user for permission to initiate connection to a URL.
InternetDial Initiates a connection to the Internet using a modem connection.
InternetAutodial Automatically causes the modem to dial the default Internet connection.
InternetSetDialState Sets the modem dialing state.
InternetHangUp Instructs the modem to disconnect from the Internet.
InternetAutodialHangup Disconnects an automatic dial-up connection.
InternetCheckConnection Allows an application to check if a connection to the Internet can be established.
InternetGetConnectedState Retrieves the connected state of the local system.

From its first release, WinInet has cached the network data it receives. The APIs that let you access the cached data are known as the URL Cache APIs. The Windows NT 5.0 WININET.DLL adds several new caching APIs (see Figure 6). Without going into too much detail, the new APIs are intended primarily for use by applications such as offline browsers. An offline browser downloads multiple related HTML pages and stores them in the file system. Later, when the computer isn’t connected to the net (for instance, when you’re at 30,000 feet), you can still “browse” the portions of the Web that were retrieved earlier. To help organize related cached data, WinInet adds the notion of a GROUPID. Check the documentation for CreateUrlCacheGroup and friends if you’re interested in this sort of thing.

Figure 6: New WinInet URL Cache APIs

GetUrlCacheEntryInfoEx Searches for the URL after translating any cached redirections that would be applied in offline mode by HttpSendRequest.
FindFirstUrlCacheEntryEx / Starts a filtered enumeration of
FindNextUrlCacheEntryEx the cache.
FindFirstUrlCacheContainer /
FindNextUrlCacheContainer
CreateUrlCacheGroup Generates cache group identifications.
SetUrlCacheEntryGroup Adds entries to or removes
DeleteUrlCacheGroup entries from a cache group.

Shell Light Weight API

One of the coolest new system DLLs in Windows NT 5.0 is SHLWAPI.DLL. (Wow, I sure sound like a geek!) Anyhow, SHLWAPI (pronounced “shell-wappi”) stands for Shell Light Weight API. Even though SHLWAPI.DLL is only 90KB (so far), it packs a ton of useful “Why didn’t they do this three years ago?” functionality into its APIs. The APIs are in four categories: C/C++ string functions, path manipulation, registry access, and miscellaneous.

Many SHLWAPI string functions are C++ runtime library functions that were long overdue in the Win32 API. Examples include StrDup, StrStr, and StrCmpN. Other APIs (which end with I) are case-insensitive versions of a standard C++ function, for instance StrStrI. Other new string APIs (see Figure 7) provide capabilities that aren’t in ANSI C++, but are useful nonetheless. Examples of this are StrTrim, StrToInt, and StrFromTimeInterval.

Figure 7: SHLWAPI String APIs

StrChr / StrChrI

StrCmpN / StrCmpNI

StrCSpn / StrCSpnI

StrDup

StrFormatByteSize

StrFromTimeInterval

StrIsIntlEqual

StrNCat

StrPBrk

With the SHLWAPI Path APIs (see Figure 8) you get unified methods for working with files and file paths. How often have you written code that extracts just the directory from a complete filespec? You can now have the PathRemoveFileSpec API. Likewise, how often have you written code that finds just the filename portion from a complete filespec? Now there’s PathFindFileName to do this potentially error-prone work for you (especially helpful because of the naming minefield that long filenames create).

Figure 8: SHLWAPI Path APIs

PathAddBackslash

PathAddExtension

PathAppend

PathBuildRoot

PathCanonicalize

PathCombine

PathCompactPath

PathCompactPathEx

PathCommonPrefix

PathFileExists

PathFindExtension

PathFindFileName

PathFindNextComponent

PathFindOnPath

PathGetArgs

PathGetDriveNumber

PathIsDirectory

PathIsFileSpec

PathIsPrefix

PathIsRelative

PathIsRoot

PathIsSameRoot

PathIsUNC

PathIsUNCServer

PathIsUNCServerShare

PathIsContentType

PathIsURL

PathMakePretty

PathMatchSpec

PathParseIconLocation

PathQuoteSpaces

PathRelativePathTo

PathRemoveArgs

PathRemoveBackslash

PathRemoveBlanks

PathRemoveExtension

PathRemoveFileSpec

PathRenameExtension

PathSearchAndQualify

PathSetDlgItemPath

PathSkipRoot

PathStripPath

PathStripToRoot

PathUnquoteSpaces

Most of the SHLWAPI Path APIs names are self explanatory if you mentally strip off the “Path” part of the name. For example, PathAddBackSlash adds a backslash. The “Path” part of the name is just a common prefix. In the few cases where the API name doesn’t give its intended use away, the API probably does something really worthwhile. For example, PathCompactPath strips out enough characters from the middle of a path so that the resultant string will fit into a given screen space. (The stripped out characters are replaced by ellipsis.) All in all, the SHLWAPI Path APIs are very cool stuff.

Although the Win32 registry is very flexible, most of us only use a few of the numerous registry APIs and we use them in fairly standard ways. The SHLWAPI registry APIs reduce much of the tedium if you’re using the registry in standard ways:

SHDeleteEmptyKey

SHDeleteKey

SHDeleteValue

SHGetValue

SHSetValue

SHQueryValueEx

SHEnumKeyEx

SHEnumValue

SHQueryInfoKey

SHRegGetBoolUSValue

To read a value of a particular subkey, it gets tedious to open the registry subkey, call RegQueryValueEx, and then close the registry key. SHGetValue does everything in one step. Enumerating and delet-ing subkeys is also a hassle using the standard registry APIs. SHLWAPI.DLL has easy solutions for these scenarios as well.

There are only two miscellaneous SHLWAPI APIs. SHOpenRegStream opens up a registry value for reading using the IStream interface. Yes, I think it’s a bit weird, but it sort of make sense if you’re reading from a value containing gobs of data. Think of it this way: configuration and settings of COM objects are usually saved with IPersistStream, and an appropriate place to save is HKEY_CURRENT_USER. So SHOpenRegStream bridges that gap. SHCreateShellPalette takes an HDC and creates an HPALETTE using the palette associated with the DC. This is the system’s shell palette. The halftone palettes on Windows 95 and on Windows NT differ, so writing an application that works well on both for all your bitmaps is sometimes hard. This can be used on both platforms.

New Stuff in IMAGEHLP.DLL

While IMAGEHLP.DLL hasn’t undergone any dramatic changes for Windows NT 5.0, there is one new group of APIs that a few developers should be overjoyed with. Ever since I wrote about IMAGEHLP’s symbol table capabilities, developers have told me “That’s great, but how can I get the source line associated with an address?” This glaring omission in the IMAGEHLP API has finally been corrected in the Windows NT 5.0 version.

All of the new IMAGEHLP line number APIs work with a new data structure called an IMAGEHLP_LINE, which contains both an address and a source file/line number pair. The API most developers will use is SymGetLineFromAddr, which takes a process handle, an address, and fills in an IMAGEHLP_LINE from them. In reverse, SymGetLineFromName uses a source file/line number to look up the corresponding address, again returning the whole ball of wax in an IMAGEHLP_LINE struct. This API will be primarily of use to debugger writers, who need to set source level breakpoints. The last two APIs in this set are SymGetLinePrev and SymGetLineNext, which provide first/next enumeration through the set of IMAGEHLP_LINE structures associated with the process.

The New Task Scheduling Interfaces

One of the first things I noticed about Windows NT 5.0 is that it added the ability to schedule tasks. If you’ve used the CRON utility with Unix, you’re familiar with the concept. To the user, this feature is seen as the Scheduled Tasks application. This program is invoked via the Start menu, under Programs | Administrative Tools | Scheduled Tasks. There’s even a Scheduled Tasks Wizard to guide the user through creating a new task.

A task can be run once at a particular time, or can be set up to execute on a repeating basis. Scheduled tasks can even be used to start programs on other machines. Imagine the ability to automatically reboot the boss’s computer 10 minutes before the weekly status meeting. Of course, it wouldn’t really be this easy, since scheduled tasks are subject to all the standard Windows NT security requirements.

The Scheduled Tasks capability is built atop standard operating system functionality. A program called MSTASK.EXE runs at startup, and is responsible for firing off tasks at the appropriate time. The properties of each individual task are stored in a file with the .JOB extension that MSTASK reads. All the .JOB files are kept in a common directory. The location of the .JOB directory can be found in the registry value: HKEY_LOCAL_MACHINE\ SOFTWARE\Microsoft\SchedulingAgent\TasksFolder.

Of course, we programmers always want programmatic access to system goodies like this, and Windows NT doesn’t disappoint. A new header file, MSTASK.H, provides the definition for these new COM interfaces:

ITaskScheduler

ITask

ITaskTrigger

IScheduledWorkItem

IEnumWorkItems

IProvideTaskPage

The ITaskScheduler interface is the most fundamental of the Task Scheduler interfaces. ITaskScheduler lets you add, delete, change, and enumerate tasks, as well as administer tasks on other machines. Obtaining an ITaskScheduler instance is easy. Simply call CoCreateInstance, specifying CLSID_CTaskScheduler as the CLSID and IID_ITaskScheduler as the IID. Here are the ITaskScheduler interfaces.

SetTargetComputer / GetTargetComputer

Enum

Activate

Delete

NewWorkItem / AddWorkItem

IsOfType

The next important interface to understand is ITask. ITask is what you use to control a task object. You can set and retrieve all of the task’s information, execute and terminate the task, add and delete triggers, and allow the user to modify the task via a property page. I’ll get to triggers momentarily.

If you dig through the definition of the ITask interface, you won’t find methods that do all the various things that I mentioned above. It turns out that ITask is derived from the IScheduledWorkItem interface. I’ve listed the methods of this interface in Figure 9. For the most part, they’re self-explanatory. Here are the additional methods that the ITask interface adds to the IScheduledWorkItem interface.

SetApplicationName / GetApplicationName

SetParameters / GetParameters

SetWorkingDirectory / GetWorkingDirectory

SetPriority / GetPriority

SetTaskFlags / GetTaskFlags

SetMaxRunTime / GetMaxRunTime

You determine when a task should run with a task trigger. A trigger is a set of criteria that, when met, causes a task to be run. There are two types of triggers: time-based and event-based. The ITaskTrigger interface is how you create and modify triggers. The IScheduledWorkItem interface contains the CreateTrigger method, which returns a pointer to an ITaskTrigger interface. There are additional methods to modify and delete an existing ITaskTrigger.

Figure 9: IscheduledWorkItem Methods

CreateTrigger / DeleteTrigger

GetTriggerCount

GetTrigger / GetTriggerString

GetRunTimes / GetNextRunTime

GetIdleWait

Run / Terminate

EditWorkItem

GetMostRecentRunTime

GetStatus / GetExitCode

SetComment / GetComment

SetCreator / GetCreator

SetWorkItemData / GetWorkItemData

SetErrorRetryCount / GetErrorRetryCount

SetErrorRetryInterval GetErrorRetryInterval

SetFlags / GetFlags

SetAccountInformation / GetAccountInformation

Time-based triggers activate at a specified point in time. Not only can you set the time that they become active, you can also have them activate once, daily, weekly, monthly, or on a specified day of the month. Event-based triggers activate in response to certain system events, like when the system starts up, when a user logs on, or when the system goes into an idle state.

The last interface of the task scheduling group is IProvideTaskPage. It allows you to display three property pages that allow the user to alter the behavior of a task. To show some of the TaskScheduler interfaces in action, I wrote the ScheduledTasks sample program shown in Figure 10. It just goes through the motions of creating the various required interface instances, and pops up the system-supplied UI that lets you edit a task’s properties.

Figure 10: ScheduledTasks.cpp

//=============================================================
// Matt Pietrek
// Microsoft Systems Journal, December 1997
// FILE: ScheduledTasks.CPP
// To compile: CL /DUNICODE=1 /D_UNICODE=1 ScheduledTasks.CPP \
//             mstask.lib ole32.lib
//=============================================================
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x500
#include <windows.h>
#include <mstask.h>
#include <tchar.h>

//=========================================================================
// Preprocessor macro that combines a bunch of often used boilerplate code.
// Prints out the results of the preceding operation, and bails out via
// _leave if the operation failed.
//=========================================================================
#define HANDLE_RESULT_CODE( x, hr ) \
        if ( SUCCEEDED(hr) ) { _tprintf( _T(“%s worked\n”), _T(#x) ); } \
        else { _tprintf( _T(“%s failed %x\n”), _T(#x), hr ); _leave; }
        
int main()
{
    CoInitialize( 0 );

    ITaskScheduler * pITaskScheduler = 0;
    ITask * pITask = 0;
    ITaskTrigger * pITaskTrigger = 0;
        
    _try
    {
        HRESULT hr;


        //=====================================================================
        // Create the ITaskScheduler interface instance
        //=====================================================================
        hr = CoCreateInstance(  CLSID_CTaskScheduler,       // REFCLSID
                                0,                          // LPUNKNOWN outer
                                CLSCTX_SERVER,              // CLSCTX
                                IID_ITaskScheduler,
                                (LPVOID *)&pITaskScheduler );
        HANDLE_RESULT_CODE( “CoCreateInstance(CLSID_CTaskScheduler)”, hr );
        
        LPWSTR ppwszComputer;
        
        //=====================================================================
        // Retrieve and print out the name of the target computer
        //=====================================================================
        pITaskScheduler->GetTargetComputer( &ppwszComputer );     
        HANDLE_RESULT_CODE( “pITaskScheduler->GetTargetComputer”, hr );
        _tprintf( _T(“Target computer: %s\n”), ppwszComputer );


        //=====================================================================
        // Free the string allocated by ::GetTargetComputer
        //=====================================================================
        CoTaskMemFree( ppwszComputer );

        
        //=====================================================================
        // Create the ITask interface instance
        //=====================================================================
        hr = pITaskScheduler->NewWorkItem(_T(“MyHappyWorkItem”),
                                            CLSID_CTask,
                                            IID_ITask,
                                            (LPUNKNOWN *)&pITask );                                 
        HANDLE_RESULT_CODE( “pITaskScheduler->NewWorkItem”, hr );


        //=====================================================================
        // Set the name of the program associated with the ITask
        //=====================================================================
        hr = pITask->SetApplicationName( _T(“CALC.EXE”) );
        HANDLE_RESULT_CODE( “pITask->SetApplicationName”, hr );
        
        
        //=====================================================================
        // Create an ITaskTrigger interface associated with the ITask
        //=====================================================================
        WORD iNewTrigger;   
        hr = pITask->CreateTrigger( &iNewTrigger, &pITaskTrigger );     
        HANDLE_RESULT_CODE( “pITask->CreateTrigger”, hr );

        TASK_TRIGGER trigger;


        //=====================================================================
        // Retrieve the default value for the task trigger
        //=====================================================================
        pITaskTrigger->GetTrigger( &trigger );
        HANDLE_RESULT_CODE( “pITaskTrigger->GetTrigger”, hr );

        trigger.wStartMinute++;     // Bump the start time up by one minute
        
        //=====================================================================
        // Store new task trigger values
        //=====================================================================
        pITaskTrigger->SetTrigger( &trigger );
        HANDLE_RESULT_CODE( “pITaskTrigger->SetTrigger”, hr );
            
        //=====================================================================
        // Display the user interface that allows the user to change task info
        //=====================================================================
        hr = pITask->EditWorkItem( 0, 0 );
        HANDLE_RESULT_CODE( “pITask->EditWorkItem”, hr );
        
        //=====================================================================
        // Get rid of the task, so that our demo doesn’t leave litter behind
        //=====================================================================
        pITaskScheduler->Delete( _T(“MyHappyWorkItem”) );
        HANDLE_RESULT_CODE( “pITaskScheduler->Delete”, hr );
    }
    __finally
    {
        if ( pITaskTrigger )
            pITaskTrigger->Release();

        if ( pITask )
            pITask->Release();

        if ( pITaskScheduler )
            pITaskScheduler->Release();
                    
        CoUninitialize();
    }
    
    return 0;
}

Wrap Up

While I’ve covered a lot of material in this article, there’s still much more that’s new in Windows NT 5.0. My intent was to highlight the significant additions and improvements to the core components, as well as some of the extended system DLLs that are commonly used. Without a doubt, Windows NT 5.0 contains goodies for almost every type of programmer, even if you don’t count technology that was slipstreamed in via service packs.

Before I finish, I’d like to thank Dan Babcock of NuMega Technologies. Dan went beyond the call of duty and provided me with a special build of SoftIce/Windows NT that worked on my pre-beta copy of Windows NT 5.0. Much of this article came from direct experimentation with Windows NT 5.0 during execution, and wouldn’t have been possible without Dan’s help.

To obtain complete source code listings, see the MSJ Web site at http://www.microsoft.com/msj.