Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Visual CSharp 2005 Recipes (2006) [eng]

.pdf
Скачиваний:
48
Добавлен:
16.08.2013
Размер:
4.04 Mб
Скачать

508C H A P T E R 1 4 W I N D O W S I N T E G R AT I O N

//Update the "last user" information with the current username.

//Specify that this should be stored as the default value

//for the key by using an empty string as the value name. Registry.SetValue(

@"HKEY_CURRENT_USER\Software\Apress\Visual C# 2005 Recipes", "", Environment.UserName, RegistryValueKind.String);

//Update the "last run" information with the current date and time.

//Specify that this should be stored as a string value in the

//registry.

Registry.SetValue(

@"HKEY_CURRENT_USER\Software\Apress\Visual C# 2005 Recipes",

"LastRun", DateTime.Now.ToString(), RegistryValueKind.String);

//Update the usage count information. Specify that this should

//be stored as an integer value in the registry. Registry.SetValue(

@"HKEY_CURRENT_USER\Software\Apress\Visual C# 2005 Recipes", "RunCount", ++runCount, RegistryValueKind.DWord);

//Wait to continue.

Console.WriteLine(Environment.NewLine); Console.WriteLine("Main method complete. Press Enter."); Console.ReadLine();

}

}

}

14-5. Search the Windows Registry

Problem

You need to search the Windows registry for a key that contains a specific value or content.

Solution

Use the Microsoft.Win32.Registry class to obtain a Microsoft.Win32.RegistryKey object that represents the root key of a registry hive you want to search. Use the members of this RegistryKey object to navigate through and enumerate the registry key hierarchy as well as to read the names and content of values held in the keys.

How It Works

You must first obtain a RegistryKey object that represents a base-level key and navigate through the hierarchy of RegistryKey objects as required. The Registry class implements a set of seven static fields that return RegistryKey objects representing base-level registry keys; Table 14-3 describes the registry location to where each of these fields maps.

C H A P T E R 1 4 W I N D O W S I N T E G R AT I O N

509

Table 14-3. Static Fields of the Registry Class

Field

Registry Mapping

ClassesRoot

HKEY_CLASSES_ROOT

CurrentConfig

HKEY_CURRENT_CONFIG

CurrentUser

HKEY_CURRENT_USER

DynData

HKEY_DYN_DATA

LocalMachine

HKEY_LOCAL_MACHINE

PerformanceData

HKEY_PERFORMANCE_DATA

Users

HKEY_USERS

 

 

 

 

Tip The static method RegistryKey.OpenRemoteBaseKey allows you to open a registry base key on a remote machine. See the .NET Framework SDK documentation for details of its use.

Once you have the base-level RegistryKey object, you must navigate through its child subkeys recursively. To support navigation, the RegistryKey class allows you to do the following:

Get the number of immediate subkeys using the SubKeyCount property.

Get a string array containing the names of all subkeys using the GetSubKeyNames method.

Get a RegistryKey reference to a subkey using the OpenSubKey method. The OpenSubKey method provides two overloads: the first opens the named key as read-only; the second accepts a bool argument that, if true, will open a writable RegistryKey object.

Once you obtain a RegistryKey, you can create, read, update, and delete subkeys and values using the methods listed in Table 14-4. Methods that modify the contents of the key require you to have a writable RegistryKey object.

Table 14-4. RegistryKey Methods to Create, Read, Update, and Delete Registry Keys and Values

Method

Description

CreateSubKey

Creates a new subkey with the specified name and returns a writable

 

RegistryKey object. If the specified subkey already exists, CreateSubKey

 

returns a writable reference to the existing subkey.

DeleteSubKey

Deletes the subkey with the specified name, which must be empty of subkeys

 

(but not values); otherwise, a System.InvalidOperationException is thrown.

DeleteSubKeyTree

Deletes the subkey with the specified name along with all of its subkeys.

DeleteValue

Deletes the value with the specified name from the current key.

GetValue

Returns the value with the specified name from the current key. The value is

 

returned as an object, which you must cast to the appropriate type. The

 

simplest form of GetValue returns null if the specified value doesn’t exist. An

 

overload allows you to specify a default value to return (instead of null) if the

 

named value doesn’t exist.

GetValueKind

Returns the registry data type of the value with the specified name in the

 

current key. The value is returned as a member of the

 

Microsoft.Win32.RegistryValueKind enumeration.

 

(Continued)

510C H A P T E R 1 4 W I N D O W S I N T E G R AT I O N

Table 14-4. Continued

Method

Description

GetValueNames

Returns a string array containing the names of all values in the current

 

registry key.

SetValue

Creates (or updates) the value with the specified name. In 2.0, you can

 

specify the data type used to store the value with the overload that takes

 

a RegistryValueKind as last parameter. If you don’t provide such a value

 

kind, one will be calculated automatically, based on the managed type of

 

the object you pass as value to set.

 

 

The RegistryKey class implements IDisposable; you should call the IDisposable.Dispose method to free operating system resources when you have finished with the RegistryKey object.

The Code

The following example takes a single command-line argument and recursively searches the CurrentUser hive of the registry looking for keys with names matching the supplied argument. When the example finds a match, it displays all string type values contained in the key to the console.

using System;

using Microsoft.Win32;

namespace Apress.VisualCSharpRecipes.Chapter14

{

class Recipe14_05

{

public static void SearchSubKeys(RegistryKey root, String searchKey)

{

// Loop through all subkeys contained in the current key. foreach (string keyname in root.GetSubKeyNames())

{

try

{

using (RegistryKey key = root.OpenSubKey(keyname))

{

if (keyname == searchKey) PrintKeyValues(key); SearchSubKeys(key, searchKey);

}

}

catch (System.Security.SecurityException)

{

//Ignore SecurityException for the purpose of the example.

//Some subkeys of HKEY_CURRENT_USER are secured and will

//throw a SecurityException when opened.

}

}

}

public static void PrintKeyValues(RegistryKey key)

{

//Display the name of the matching subkey and the number of

//values it contains.

Console.WriteLine("Registry key found : {0} contains {1} values", key.Name, key.ValueCount);

C H A P T E R 1 4 W I N D O W S I N T E G R AT I O N

511

// Loop through the values and display.

foreach (string valuename in key.GetValueNames())

{

if (key.GetValue(valuename) is String)

{

Console.WriteLine(" Value : {0} = {1}", valuename, key.GetValue(valuename));

}

}

}

public static void Main(String[] args)

{

if (args.Length > 0)

{

// Open the CurrentUser base key.

using (RegistryKey root = Registry.CurrentUser)

{

//Search recursively through the registry for any keys

//with the specified name.

SearchSubKeys(root, args[0]);

}

}

// Wait to continue. Console.WriteLine(Environment.NewLine); Console.WriteLine("Main method complete. Press Enter."); Console.ReadLine();

}

}

}

Usage

Running the example using the command Recipe14-05 Environment will display output similar to the following when executed using the command on a machine running Windows XP:

Registry key found : HKEY_CURRENT_USER\Environment contains 2 values

Value : TEMP = C:\Documents and Settings\Allen\Local Settings\Temp

Value : TMP = C:\Documents and Settings\Allen\Local Settings\Temp

14-6. Create a Windows Service

Problem

You need to create an application that will run as a Windows service.

Solution

Create a class that extends System.ServiceProcess.ServiceBase. Use the inherited properties to control the behavior of your service, and override inherited methods to implement the functionality required. Implement a Main method that creates an instance of your service class and passes it to the static ServiceBase.Run method.

512 C H A P T E R 1 4 W I N D O W S I N T E G R AT I O N

Note The ServiceBase class is defined in the System.Serviceprocess.dll assembly, so you must include a reference to this assembly when you build your service class.

How It Works

To create a Windows service manually, you must implement a class derived from the ServiceBase class. The ServiceBase class provides the base functionality that allows the Windows Service Control Manager (SCM) to configure the service, operate the service as a background task, and control the life cycle of the service. The SCM also controls how other applications can control the service programmatically.

Tip If you are using Microsoft Visual Studio, you can use the Windows Service project template to create a Windows service. The template provides the basic code infrastructure required by a Windows service class, which you can extend with your custom functionality.

To control your service, the SCM uses the eight protected methods inherited from ServiceBase class described in Table 14-5. You should override these virtual methods to implement the functionality and behavior required by your service. Not all services must support all control messages. The CanXXX properties inherited from the ServiceBase class declare to the SCM which control messages your service supports; Table 14-5 specifies the property that controls each operation.

Table 14-5. Methods That Control the Operation of a Service

Method

Description

OnStart

All services must support the OnStart method, which the SCM calls to start the

 

service. The SCM passes a string array containing arguments specified for

 

the service. These arguments can be specified when the ServiceController.

 

Start method is called and are usually configured in the service’s property

 

window in Windows Control Panel. However, they are rarely used because it is

 

better for the service to retrieve its configuration information directly from

 

the Windows registry.

 

The OnStart method must normally return within 30 seconds, or the SCM will

 

abort the service. Your service must call the RequestAdditionalTime method

 

of the ServiceBase class if it requires more time; specify the additional

 

milliseconds required as an int.

OnStop

Called by the SCM to stop a service—the SCM will call OnStop only if the

 

CanStop property is set to true.

OnPause

Called by the SCM to pause a service—the SCM will call OnPause only if the

 

CanPauseAndContinue property is set to true.

OnContinue

Called by the SCM to continue a paused service—the SCM will call OnContinue

 

only if the CanPauseAndContinue property is set to true.

OnShutdown

Called by the SCM when the system is shutting down—the SCM will call

 

OnShutdown only if the CanShutdown property is set to true.

OnPowerEvent

Called by the SCM when a system-level power status change occurs, such as

 

a laptop going into suspend mode. The SCM will call OnPowerEvent only if the

 

CanHandlePowerEvent property is set to true.

C H A P T E R 1 4 W I N D O W S I N T E G R AT I O N

513

Method

Description

OnCustomCommand Allows you to extend the service control mechanism with custom control messages; see the .NET Framework SDK documentation for more details.

OnSessionChange Called by the SCM when a change event is received from the Terminal Services session or when users log on and off on the local machine.

A System.ServiceProcess.SessionChangeDescription object passed as an argument by the SCM contains details of what type of session change occurred. The SCM will call OnSessionChange only if the CanHandleSessionChangeEvent property is set to true. This method is new in the .NET Framework 2.0.

As mentioned in Table 14-5, the OnStart method is expected to return within 30 seconds, so you should not use OnStart to perform lengthy initialization tasks where you can avoid it. A service class should implement a constructor that performs initialization, including configuring the inherited properties of the ServiceBase class. In addition to the properties that declare the control messages supported by a service, the ServiceBase class implements three other important properties:

ServiceName is the name used internally by the SCM to identify the service and must be set before the service is run.

AutoLog controls whether the service automatically writes entries to the event log when it receives any of the OnStart, OnStop, OnPause, and OnContinue control messages from Table 14-5.

EventLog provides access to an EventLog object that’s preconfigured with an event source name that’s the same as the ServiceName property registered against the Application log. (See recipe 14-3 for more information about the EventLog class.)

The final step in creating a service is to implement a static Main method. The Main method must create an instance of your service class and pass it as an argument to the static method

ServiceBase.Run.

The Code

The following Windows service example uses a configurable System.Timers.Timer to write an entry to the Windows event log periodically. You can start, pause, and stop the service using the Services application in the Control Panel.

using System;

using System.Timers;

using System.ServiceProcess;

namespace Apress.VisualCSharpRecipes.Chapter14

{

class Recipe14_06 : ServiceBase

{

//A Timer that controls how frequently the example writes to the

//event log.

private System.Timers.Timer timer;

public Recipe14_06()

{

// Set the ServiceBase.ServiceName property. ServiceName = "Recipe 14_06 Service";

514C H A P T E R 1 4 W I N D O W S I N T E G R AT I O N

//Configure the level of control available on the service. CanStop = true;

CanPauseAndContinue = true; CanHandleSessionChangeEvent = true;

//Configure the service to log important events to the

//Application event log automatically.

AutoLog = true;

}

//The method executed when the timer expires and writes an

//entry to the Application event log.

private void WriteLogEntry(object sender, ElapsedEventArgs e)

{

//Use the EventLog object automatically configured by the

//ServiceBase class to write to the event log. EventLog.WriteEntry("Recipe14_06 Service active : " + e.SignalTime);

}

protected override void OnStart(string[] args)

{

//Obtain the interval between log entry writes from the first

//argument. Use 5000 milliseconds by default and enforce a 1000

//millisecond minimum.

double interval;

try

{

interval = Double.Parse(args[0]); interval = Math.Max(1000, interval);

}

catch

{

interval = 5000;

}

EventLog.WriteEntry(String.Format("Recipe14_06 Service starting. " + "Writing log entries every {0} milliseconds...", interval));

//Create, configure, and start a System.Timers.Timer to

//periodically call the WriteLogEntry method. The Start

//and Stop methods of the System.Timers.Timer class

//make starting, pausing, resuming, and stopping the

//service straightforward.

timer = new Timer(); timer.Interval = interval; timer.AutoReset = true;

timer.Elapsed += new ElapsedEventHandler(WriteLogEntry); timer.Start();

}

protected override void OnStop()

{

EventLog.WriteEntry("Recipe14_06 Service stopping..."); timer.Stop();

C H A P T E R 1 4 W I N D O W S I N T E G R AT I O N

515

// Free system resources used by the Timer object. timer.Dispose();

timer = null;

}

protected override void OnPause()

{

if (timer != null)

{

EventLog.WriteEntry("Recipe14_06 Service pausing..."); timer.Stop();

}

}

protected override void OnContinue()

{

if (timer != null)

{

EventLog.WriteEntry("Recipe14_06 Service resuming..."); timer.Start();

}

}

protected override void OnSessionChange(SessionChangeDescription change)

{

EventLog.WriteEntry("Recipe14_06 Session change..." + change.Reason);

}

public static void Main()

{

//Create an instance of the Recipe14_06 class that will write

//an entry to the Application event log. Pass the object to the

//static ServiceBase.Run method.

ServiceBase.Run(new Recipe14_06());

}

}

}

Usage

If you want to run multiple services in a single process, you must create an array of ServiceBase objects and pass it to the ServiceBase.Run method. Although service classes have a Main method, you can’t execute service code directly; attempting to run a service class directly results in Windows displaying the Windows Service Start Failure message box, as shown in Figure 14-1. Recipe 14-7 describes what you must do to install your service before it will execute.

Figure 14-1. The Windows Service Start Failure message box

516 C H A P T E R 1 4 W I N D O W S I N T E G R AT I O N

14-7. Create a Windows Service Installer

Problem

You have created a Windows service application and need to install it.

Solution

Add a new class to your Windows service project that extends the System.Configuration.Install. Installer class to create an installer class containing the information necessary to install and configure your service class. Use the Installer tool (Installutil.exe) to perform the installation, which is installed as part of the .NET Framework.

Note You must create the installer class in the same assembly as the service class for the service to install and function correctly.

How It Works

As recipe 14-6 points out, you cannot run service classes directly. The high level of integration with the Windows operating system and the information stored about the service in the Windows registry means services require explicit installation.

If you have Microsoft Visual Studio .NET, you can create an installation component for your service automatically by right-clicking in the design view of your service class and selecting Add Installer from the context menu. You can call this installation component by using deployment projects or by using the Installer tool to install your service. You can also create installer components for Windows services manually by following these steps:

1.In your project, create a class derived from the Installer class.

2.Apply the attribute System.ComponentModel.RunInstallerAttribute(true) to the installer class.

3.In the constructor of the installer class, create a single instance of the System.ServiceProcess. ServiceProcessInstaller class. Set the Account, User, and Password properties of

ServiceProcessInstaller to configure the account under which your service will run. This account must already exist.

4.In the constructor of the installer class, create one instance of the System.ServiceProcess. ServiceInstaller class for each individual service you want to install. Use the properties of the ServiceInstaller objects to configure information about each service, including the following:

ServiceName, which specifies the name Windows uses internally to identify the service. This must be the same as the value assigned to the ServiceBase.ServiceName property.

DisplayName, which provides a user-friendly name for the service.

StartType, which uses values of the System.ServiceProcess.ServiceStartMode enumeration to control whether the service is started automatically or manually or is disabled.

ServiceDependsUpon, which allows you to provide a string array containing a set of service names that must be started before this service can start.

5.Add the ServiceProcessInstaller object and all ServiceInstaller objects to the System. Configuration.Install.InstallerCollection object accessed through the Installers property, which is inherited by your installer class from the Installer base class.

C H A P T E R 1 4 W I N D O W S I N T E G R AT I O N

517

The Code

The following example is an installer for the Recipe14_06 Windows service created in recipe 14-6. The sample project contains the code from recipe 14-6 and for the installer class. This is necessary for the service installation to function correctly. To compile the example, you must reference two additional assemblies: System.Configuration.Install.dll and System.ServiceProcess.dll.

using System.Configuration.Install; using System.ServiceProcess;

using System.ComponentModel;

namespace Apress.VisualCSharpRecipes.Chapter14

{

[RunInstaller(true)]

public class Recipe14_07 : Installer

{

public Recipe14_07()

{

//Instantiate and configure a ServiceProcessInstaller. ServiceProcessInstaller ServiceExampleProcess =

new ServiceProcessInstaller(); ServiceExampleProcess.Account = ServiceAccount.LocalSystem;

//Instantiate and configure a ServiceInstaller. ServiceInstaller ServiceExampleInstaller =

new ServiceInstaller(); ServiceExampleInstaller.DisplayName =

"Visual C# 2005 Recipes Service Example"; ServiceExampleInstaller.ServiceName = "Recipe 14_06 Service"; ServiceExampleInstaller.StartType = ServiceStartMode.Automatic;

//Add both the ServiceProcessInstaller and ServiceInstaller to

//the Installers collection, which is inherited from the

//Installer base class. Installers.Add(ServiceExampleInstaller); Installers.Add(ServiceExampleProcess);

}

}

}

Usage

To install the Recipe14_06 service, build the project, navigate to the directory where Recipe14-07.exe is located (bin\debug by default), and execute the command Installutil Recipe14-07.exe. You can then see and control the Recipe14_06 service using the Windows Computer Management console.

However, despite specifying a StartType of Automatic, the service is initially installed unstarted; you must start the service manually (or restart your computer) before the service will write entries to the event log. Once the service is running, you can view the entries it writes to the Application event log using the Event Viewer application. To uninstall the Recipe14_06 service, add the /u switch to the

Installutil command as follows: Installutil /u Recipe14-07.exe.

Tip If you have the Service application from the Control Panel open when you uninstall the service, the service will not uninstall completely until you close the Service application. Once you close the Service application, you can reinstall the service; otherwise, you will get an error telling you that the installation failed because the service is scheduled for deletion.

Соседние файлы в предмете Программирование на C++