unit PortTalk;
// ****************************************************************************
// TPortTalk - jvn@vns - 2006 June 8
// ****************************************************************************
// This unit is based on and uses work of Craig Peacock - 13th January 2002
// 'Beyond Logic Port Talk I/O Port Driver' - http://www.beyondlogic.org
// All credits to him for the PortTalk driver and associated sources.
// Porttalk.sys is a kernel driver (ring 0) that allows programs access or
// exclusive access to IO Ports on a Windows NT/2000/XP system.
// The _PortTalk22Src.zip contains the original sources of both the driver
// itself (Microsoft DDK required for compilation) docs and examples.
// A monolitic test example for use with the free Borland BCC55 compiler
// is added by me under the _PortTalkVnsEx subdir in the ZIP.
// ****************************************************************************
// Additional reference material on device drivers: www.catch22.net (tutorials)
// www.osronline.com - www.sysinternals.com (utilities) - www.codeproject.com -
// delphi.about.com - www.techtricks.com - www.koders.com - and of course MSDN.
// ****************************************************************************
// This TPortTalk class for Delphi is my own design and implements an IOCTL
// call wrapper for the porttalk driver. The class is designed to automatically
// create and install the driver at start of use and to clean up everything
// afterwards. This requires Administration rights of the application user.
// An Admin facility is present to manually load/unload the driver under Admin
// log-in in order to make it available to PortTalk when running later for a
// normal User without admin rights.
// The driver itself is linked into the unit as a resource so that no extra
// files are required.
// ****************************************************************************
// Changes - jvn@vns - 2006 may 27
// - Added simple OS detection and port read/write for Win9x.
// - Added IsWinNTAdmin detection plus improved Admin functionality.
// Changes - jvn@vns - 2006 may 29
// - Reporter property changed into TStrings
// Changes - jvn@vns - 2006 may 30
// - Further improvement of Admin options.
// Changes - jvn@vns - 2006 june 8
// - Added PortTalk Version; Improved mechanism for normal (non-amin) User
// Changes - jvn@vns - 2006 june 21
// - Added VSS keywords to complement the internal Version
// ****************************************************************************
interface
uses
Classes;
{*****************************************************************************}
{* VISUAL SOURCESAFE EXPANDED KEYWORDS *}
{*****************************************************************************}
{These entries are automatically maintained at Check-in by Visual SourceSafe
by the keyword expansion mechanism. This information is made available to
code for runtime version information}
const
cptVSSArchive = '$Archive: /DRPP/VnsVcl/VnsVclSrc/PortTalk.pas $';
cptVSSAuthor = '$Author: Jack Van Nuenen $';
cptVSSDate = '$Date: 6/21/06 2:54p $';
cptVSSRevision = '$Revision: 3 $';
{No further keyword expansion from here onwards}
{-$NoKeyWords:$-}
{*****************************************************************************}
{* TYPE AND CLASS DECLARATIONS *}
{*****************************************************************************}
type
TPortTalk = class (TObject)
private
fOpen: Boolean;
fSrvStarted: Boolean;
fSrvInstalled: Boolean;
fHandle: THandle;
fReporter: TStrings;
fIsWinNT: Boolean;
fIsWinNTAdmin: Boolean;
fVersion: string;
procedure Report(S: string);
function ReportNotOpen(S: string): Boolean;
function MyOpenSCManager: Cardinal;
function MyDriverFileName: string;
function StartPTService(MayInstall: Boolean): Boolean;
function StopPTService(AndDelete: Boolean): Boolean;
public
constructor Create;
destructor Destroy; override;
property Reporter: TStrings read fReporter write fReporter;
property Active: Boolean read fOpen;
property IsWinNT: Boolean read fIsWinNT;
property IsWinNTAdmin: Boolean read fIsWinNTAdmin;
property Version: string read fVersion;
procedure Admin;
procedure Open;
procedure Close;
procedure OutPortB(PortAddress: Word; Data: Byte);
function InPortB(PortAddress: Word; ClosedResult: Byte): Byte;
end;
implementation
uses
Windows,
WinSvc,
SysUtils,
Forms, Controls, Dialogs;
// The next resource contains in fact the binary image of the 'porttalk.sys'
// file. It has been created using the original sys file (here: with an
// underscore prepended to the name in order to distinguish this stuff from
// the delphi sources). A very simple resource script '_porttalksys.RC' has
// been made (a normal text file) with one single line in it:
// PortTalkSysData RCDATA "_porttalk.sys"
// This defines 'PortTalkSysData' as a raw data resource (RCDATA) having the
// content of our _porttalk.sys.
// Next, a simple batch file '_porttalksys_rc2res.bat' has been made, again
// with one single line in it:
// BRCC32 -foPortTalk.RES _porttalksys.rc
// calling the Borland Resource Compiler (BRCC32.exe) to compile the script
// to the corresponding resource (.RES) file. The -fo switch sets the output
// file name to PortTalk.RES (distributes better with PortTalk.Pas :-)
{$RESOURCE 'PortTalk.RES'}
// The next constants - after the version string for this unit - represent the
// names of the porttalk system driver in various contexts (as Resource,
// as Device, as Service, as Driver file name under the windows system32 dir).
const
cPortTalkVerStr = 'PortTalk V1.2';
cPortTalkDevStr = '\\.\PortTalk';
cPortTalkSrvStr = 'PortTalkSrv';
cPortTalkDrvStr = '\drivers\porttalk.sys';
cPortTalkResStr = 'PortTalkSysData';
// ****************************************************************************
// DetectIsNT and DetectIsNTAdmin
// ****************************************************************************
// Establish whether or not we're running under NT/XP/Vista
// if not, we're under Win95/98/ME or even Win3.1/Win32subsystem
// In this code we refer to the first group as 'WinNT' and to
// everything else as 'Win9x'.
function DetectIsNT: Boolean;
begin
Result:=(Win32Platform = VER_PLATFORM_WIN32_NT);
end;
// Returns a boolean indicating whether or not user has admin privileges.
// Based on example code at http://www.techtricks.com/delphi/isadmin.php
// which itself is based on Borland's Community Article #26752 at
// http://community.borland.com/article/0,1410,26752,00.html
function DetectIsNTAdmin: Boolean;
const
cTokenInfoLen = 1024;
SECURITY_NT_AUTHORITY: SID_IDENTIFIER_AUTHORITY = (Value: (0,0,0,0,0,5));
SECURITY_BUILTIN_DOMAIN_RID : DWORD = $00000020;
DOMAIN_ALIAS_RID_ADMINS : DWORD = $00000220;
//DOMAIN_ALIAS_RID_USERS : DWORD = $00000221;
//DOMAIN_ALIAS_RID_GUESTS : DWORD = $00000222;
//DOMAIN_ALIAS_RID_POWER_ : DWORD = $00000223;
var
TokenHandle: THandle;
pTokGroups: pTokenGroups;
RetLen: Cardinal;
psidAdmins: PSID;
I: Integer;
Res: Boolean;
begin
Result:=False;
// Can't do following stuff without having NT or up.
if not DetectIsNT then Exit;
Res:=OpenThreadToken(GetCurrentThread, TOKEN_QUERY, True, TokenHandle);
if not Res and (GetLastError=ERROR_NO_TOKEN)
then Res:=OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, TokenHandle);
if not Res then Exit;
GetMem(pTokGroups, cTokenInfoLen);
try
Res:=GetTokenInformation(TokenHandle, TokenGroups,
pTokGroups, cTokenInfoLen, RetLen);
CloseHandle(TokenHandle);
if Res then
begin
AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2,
SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0, psidAdmins);
for I:=0 to pTokGroups.GroupCount-1 do
if EqualSid(psidAdmins, pTokGroups.Groups[I].Sid ) then
begin
Result:=True;
Break;
end;
FreeSid(psidAdmins);
end;
finally
FreeMem(pTokGroups);
end;
end;
// ****************************************************************************
// Create/Destroy
// ****************************************************************************
constructor TPortTalk.Create;
begin
inherited Create;
// Adopt Version
fVersion:=cPortTalkVerStr;
// Detections
fIsWinNT:=DetectIsNT;
fIsWinNTAdmin:=DetectIsNTAdmin;
// Do NOT Open automatically as it may have side-effects and
// also to allow a reporter handler to be attached first
end;
destructor TPortTalk.Destroy;
begin
// Kill reporting and then Close
fReporter:=nil;
Close;
inherited Destroy;
end;
// ****************************************************************************
// Reporting
// ****************************************************************************
// Report a string
procedure TPortTalk.Report(S: string);
begin
// Reporter handler must be assigned.
if not Assigned(fReporter) then Exit;
// Output string prefixed with 'PortTalk ';
fReporter.Add('PortTalk '+S);
end;
// Report a string when PortTalk not Open and return not Open.
function TPortTalk.ReportNotOpen(S: string): Boolean;
begin
// Return True when PortTalk is NOT Open
Result:=not fOpen;
// Report if not Open with given string
if fOpen then Exit;
if S<>'' then S:=' for '+S;
S:=S+'.';
Report('not Open.'+S);
end;
// ****************************************************************************
// Open/Close
// ****************************************************************************
// Open PortTalk. This will use the PortTalk Service if it is already
// present and started. If not, and the program is run by a user with
// Administrative Rights, it will automatically try to start the Service.
// When the Service is not present, the driver and the Service will first
// be installed.
// Under Win9x the Open/Close mechanism is strictly a logical one and
// without any actual meaning as we won't need to install the driver.
procedure TPortTalk.Open;
begin
// Exit when PortTalk is already open.
if fOpen then
begin
Report('already Open.');
Exit;
end;
// Set logal fOpen flag as only operation when under Win9x
if not fIsWinNT then
begin
fOpen:=True;
Report('Opened for Win9X.');
Exit;
end;
Report('Opening for WinNT/XP..');
if fIsWinNTAdmin
then Report('User has Administrator Rights.')
else Report('User is NOT an Administrator.');
// Clear flags that keep track of actions resulting from Opening PortTalk.
fSrvStarted:=False;
fSrvInstalled:=False;
// Open PortTalk by accessing the device as an existing 'file'
fHandle:=CreateFile(PChar(cPortTalkDevStr), GENERIC_READ,
0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
// If we cannot open it, try starting the associated Service (with
// enabled option of installing it first) and then retry to Open.
// This will fail when not having Administrator Rights, but we
// still proceed to report what happens..
if (fHandle=INVALID_HANDLE_VALUE) then
begin
if StartPTService(True)
then fHandle:=CreateFile(PChar(cPortTalkDevStr), GENERIC_READ,
0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
end;
// Evaluate result
fOpen:=(fHandle<>INVALID_HANDLE_VALUE);
if fOpen
then Report('Opened for WinNT/XP.')
else Report('cannot be used.');
end;
// Close PortTalk. This will try to leave the system as it was encountered
// when calling Open. Under Administartive Rights, it will stop the Service
// when it was found existing but stopped when calling Open. It will delete
// the Service and remove the driver when those were not present when
// calling Open.
// Under Win9x, the Close action is logical only
procedure TPortTalk.Close;
begin
if ReportNotOpen('') then Exit;
Report('Closing..');
fOpen:=False;
// Nothing to do further when under Win9x
if not IsWinNT then Exit;
// Close the PortTalk file handle
CloseHandle(fHandle);
fHandle:=INVALID_HANDLE_VALUE;
// Stop the Service when it was started from here
// and Delete it when it was installed from here
if fSrvStarted then StopPTService(fSrvInstalled);
Report('Closed.');
end;
// ****************************************************************************
// StartPTService/StopPTService
// ****************************************************************************
// Some common stuff first..
function TPortTalk.MyOpenSCManager: Cardinal;
var
DesiredAccess: cardinal;
begin
// Open Handle to Service Control Manager. MachineName=nil for local,
// DatabaseName=nil for default, DesiredAccess as appropriate
// Only an Admin can open the SCM with SC_MANAGER_ALL_ACCESS and have
// an SCM handle that can be used with CreateService. Normal User can use
// DesiredAccess:=SC_MANAGER_CONNECT or SC_MANAGER_ENUMERATE_SERVICE;
// bute the SCM handle will not be usable for Service control
DesiredAccess:=SC_MANAGER_ALL_ACCESS;
Result:=OpenSCManager(nil, nil, DesiredAccess);
// Test result
if (Result>0) then Exit;
// Report when not successfull
if GetLastError=ERROR_ACCESS_DENIED
then Report('insufficent rights to open Service Control Manager')
else Report('cannot open the Service Control Manager');
end;
// Return the full file specifier of the Driver
// It is supposed to reside in de drivers subdir under the
// system dir (typically: c:\windows\system32\)
function TPortTalk.MyDriverFileName: string;
var
Buf: array[0..100] of char;
begin
GetSystemDirectory(Buf, SizeOf(Buf));
Result:=Buf+cPortTalkDrvStr;
end;
// Start the PortTalk Service. Optionally install it when not yet present.
// This sets the fSrvStarted en fSrvInstalled flags when appropriate.
// Return True when the Service is started from here (= value of fSrvStarted)
function TPortTalk.StartPTService(MayInstall: Boolean): Boolean;
var
hSCManager: Cardinal;
hService: Cardinal;
SArgVectors: PChar;
DoInstall: Boolean;
DrvFName: TFileName;
rStream: TResourceStream;
DriverPlaced: Boolean;
begin
Result:=False;
// Open the Service Control Manager.
hSCManager:=myOpenSCManager;
// Test result
if (hSCManager=0) then
begin
Report('Service cannot be started.');
Exit;
end;
// Open a Handle to the PortTalk Service Database, taking a handle to the
// SCM, a pointer to the name of the service to start and the desired access.
hService:=OpenService(hSCManager, PChar(cPortTalkSrvStr), SERVICE_ALL_ACCESS);
// Test result of the operation
DoInstall:=False;
if (hService=0) then
begin
case GetLastError of
ERROR_ACCESS_DENIED:
Report('Service cannot Open; Right Denied.');
ERROR_INVALID_NAME:
Report('Service cannot Open; Name Invalid.');
ERROR_SERVICE_DOES_NOT_EXIST:
begin
if MayInstall
then Report('Service cannot Open. Starting Installation.')
else Report('Service cannot Open.');
DoInstall:=MayInstall;
end;
end;
end;
// If we now have DoInstall then Install the driver and the Service
if DoInstall then
begin
DrvFName:=MyDriverFileName;
// Extract the driver from the exe's resources
// and 'materialize' in in window's system32\drivers directory
rStream:=TResourceStream.Create(hInstance, cPortTalkResStr, RT_RCDATA);
try
DriverPlaced:=True;
try
rStream.SaveToFile(DrvFName);
except
DriverPlaced:=False;
end;
finally
rStream.Free;
end;
// Report on Driver placement
if DriverPlaced
then Report('Driver installed as '+DrvFName)
else Report('Driver could not be installed.');
// Create the PortTalk Service. This will create the appropriate keys in
// the registry at HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services
// Note: doesn't care here whether or not driver exists or path ok
// Warning: Using anything other than SERVICE_DEMAND_START for the start
// type (except SERVICE_DISABLED) causes the driver to be loaded at boot
// time which may be fatal if the driver contains bugs..
hService:=CreateService (
hSCManager, // SCManager database
PChar(cPortTalkSrvStr), // name of service
PChar(cPortTalkSrvStr), // name to display
SERVICE_ALL_ACCESS, // desired access
SERVICE_KERNEL_DRIVER, // service type
SERVICE_DEMAND_START, // start type
SERVICE_ERROR_NORMAL, // error control type
PChar(DrvFName), // path to service's binary
nil, // no load ordering group
nil, // no tag identifier
nil, // no dependencies
nil, // LocalSystem account
nil); // no password
// Report on Service installation
fSrvInstalled:=(hService<>0);
if fSrvInstalled
then Report('Service successfully installed.')
else if GetLastError=ERROR_SERVICE_EXISTS
then Report('Service was already installed.')
else Report('Service could not be created.');
end;
// If we now have a valid Service Handle, then start the PortTalk
// Service with no arguments (zero count, nil pointer)
if (hService<>0) then
begin
SArgVectors:=nil;
fSrvStarted:=StartService(hService, 0, SArgVectors);
// Reporting
if fSrvStarted
then Report('Service has been successfully started.')
else if GetLastError=ERROR_SERVICE_ALREADY_RUNNING
then Report('Service is already running.')
else Report('driver cannot start by unknown error.');
// Return Result
Result:=fSrvStarted;
end;
// Close the Service Control Manager
CloseServiceHandle(hService);
end;
// Stop and optionally delete the PortTalk Service
// Return True when the Service is actually stopped from here
// This does NOT clear any of the fSrvStarted or fSrvInstalled flags.
function TPortTalk.StopPTService(AndDelete: Boolean): Boolean;
var
hSCManager: Cardinal;
hService: Cardinal;
ServiceStatus: _SERVICE_STATUS;
DrvFName: TFileName;
begin
Result:=False;
// Open the Service Control Manager.
hSCManager:=myOpenSCManager;
// Test result
if (hSCManager=0) then
begin
Report('Service cannot be stopped.');
Exit;
end;
// Open access to the PortTalk Service (if present)
hService:=OpenService(hSCManager, PChar(cPortTalkSrvStr), SERVICE_ALL_ACCESS);
if (hService=0) then
begin
case GetLastError of
ERROR_ACCESS_DENIED:
Report('Service cannot Open; Right Denied.');
ERROR_INVALID_NAME:
Report('Service cannot Open; Name Invalid.');
ERROR_SERVICE_DOES_NOT_EXIST:
Report('Service is not installed.');
end;
Exit;
end;
// Arriving here, the PortTalk Service _is_ installed; Stop the Service
Result:=ControlService(hService, SERVICE_CONTROL_STOP, ServiceStatus);
if Result
then Report('Service has been successfully stopped.')
else if GetLastError=ERROR_SERVICE_NOT_ACTIVE
then Report('Service was already stopped.')
else Report('Service could not be stopped.');
// When having AndDelete then Delete the Service (and the registry keys)
// as well as the driver file itself
if AndDelete then
begin
// Delete the Service
if DeleteService(hService)
then Report('Service has been successfully deleted.')
else Report('Service deletion gave an error.');
// Delete the Driver file
DrvFName:=MyDriverFileName;
if DeleteFile(DrvFName)
then Report('Driver removed - '+DrvFName)
else Report('Driver could not be removed.')
end;
// Close Handle to Porttalk Service Database
CloseServiceHandle(hService);
// Close Service Control Manager
CloseServiceHandle(hSCManager);
end;
// ****************************************************************************
// OutPort/InPort
// ****************************************************************************
// Some ported excerpts from winioctl.h
const
IOCTL_USER_CODES = $0800;
METHOD_BUFFERED = 0;
METHOD_IN_DIRECT = 1;
METHOD_OUT_DIRECT = 2;
METHOD_NEITHER = 3;
FILE_ANY_ACCESS = 0;
FILE_READ_ACCESS = 1;
FILE_WRITE_ACCESS = 2;
function CTL_CODE(aDeviceType, aFunction, aMethod, aAccess: Integer): Integer;
begin
Result:=(aDeviceType shl 16) or (aAccess shl 14)
or (aFunction shl 2) or aMethod;
end;
// Next couple of constants derived from PortTalk_IOCTL.h
// Note: the equivalent identifiers there are in fact CTL_CODE macro's
const
cPORTTALKTYPE = 40000;
cIOCTL_READ_PORT_UCHAR = $904;
cIOCTL_WRITE_PORT_UCHAR = $905;
// OutPort and InPort for Bytes
// Using IOCTL interface with the PortTalk system driver under WinNT
// Using direct port write/read under Win98
procedure TPortTalk.OutPortB(PortAddress: Word; Data: Byte);
var
Buf: array[0..2] of Byte;
CtrlCode: Cardinal;
BytesRet: Cardinal;
begin
if ReportNotOpen('OutPortB') then Exit;
if fIsWinNT then
begin
Buf[0]:=Lo(PortAddress);
Buf[1]:=Hi(PortAddress);
Buf[2]:=Data;
CtrlCode:=CTL_CODE(cPORTTALKTYPE, cIOCTL_WRITE_PORT_UCHAR,
METHOD_BUFFERED, FILE_ANY_ACCESS);
if not DeviceIOControl(fHandle, CtrlCode, @Buf, 3, nil, 0, BytesRet, nil)
then Report('OutPortB Error: '+IntToStr(GetLastError));
end else
begin
// Direct port access under Win9x (works in 16 and 32bit mode)
asm
mov dx,PortAddress
mov al,Data
out dx,al
end;
end;
end;
// Read Byte from Port. The value of ClosedResult will be returned when
// PortTalk is not Open.
function TPortTalk.InPortB(PortAddress: Word; ClosedResult: Byte): Byte;
var
Buf: array[0..2] of Byte;
CtrlCode: Cardinal;
BytesRet: Cardinal;
begin
Result:=ClosedResult;
if ReportNotOpen('InPortB') then Exit;
if fIsWinNT then
begin
CtrlCode:=CTL_CODE(cPORTTALKTYPE, cIOCTL_READ_PORT_UCHAR,
METHOD_BUFFERED, FILE_ANY_ACCESS);
Buf[0]:=Lo(PortAddress);
Buf[1]:=Hi(PortAddress);
if DeviceIOControl(fHandle, CtrlCode, @Buf, 2, @Buf, 1, BytesRet, nil)
then Result:=Buf[0]
else Report('InPortB Error: '+IntToStr(GetLastError));
end else
begin
// Direct port access under Win9x (works in 16 and 32bit mode)
asm
mov dx,PortAddress
in al,dx
mov @Result,al
end;
end;
end;
// ****************************************************************************
// Permissions
// ****************************************************************************
const
cIOCTL_IOPM_RESTRICT_ALL_ACCESS = $900;
cIOCTL_IOPM_ALLOW_EXCUSIVE_ACCESS = $901;
cIOCTL_SET_IOPM = $902;
cIOCTL_ENABLE_IOPM_ON_PROCESSID = $903;
{ *** NOT IMPLEMENTED ***
The functionality the PortTalk.sys driver extends beyond simple port access.
It also allows modification of the I/O Permissions Bitmap and can it grant
full or specific IO permissions to all or specific processes.
The interface routines for this functionality are currently not implemented
in this TPortTalk Delphi class.
}
// ****************************************************************************
// Admin
// ****************************************************************************
// This procedure can be used to manually install or remove the PortTalk
// driver and Service while logged-in as an Administrator, in order to make
// make the Service available to PortTalk while logged-in as a normal User.
// This procedure is intended to be stand-alone, using it's own dialogs,
// in order to offer a simple method to any program using TPortTalk to have
// the possibility of separate driver/service installation/removal.
procedure TPortTalk.Admin;
const
cNL = #10#13;
cTitle = ' - Driver and Service Administration.';
var
Status: string;
ModalResult: Integer;
begin
// Report that this procedure is beginning..
Report('< Admin Begin >');
// Every dialog shows a first line with Version and cTitle..
Status:=Version+cTitle+cNL+cNL;
if not fIsWinNTAdmin then
begin
// Not an Admin or not NT - PortTalk can be Active (Open) or not
// but we cannot change anything regarding driver or service.
if fOpen
then Status:=Status+'PortTalk is currently Active.'+cNL
else Status:=Status+'PortTalk is NOT Active.'+cNL;
if fIsWinNT
then MessageDlg(Status+
'Admin functionality requires log-in as Administrator.',
mtInformation, [mbOk], 0)
else MessageDlg(Status+
'Admin functionality is not required under Win9X.',
mtInformation, [mbOk], 0);
end else if fOpen then
begin
// WinNT - WinNTAdmin - PortTalk Active. Driver may or may not
// have been installed from here.. Whatever the case, we can
// offer to de-install. Simply call Close after setting the internal
// flags to force that Close will remove service and driver.
// If installed from here, we can also offer to *keep* the driver
// and service resident after normal close of PortTalk (which
// normally will automatically occur at program termination).
Status:=Status+'PortTalk Driver IS currently installed.'+cNL;
if fSrvInstalled
then ModalResult:=MessageDlg(Status+
'The current process will automatically remove it again.'+cNL+
'Press [Yes] to keep installed after Close or exit.'+cNL+
'Press [No] to stop and remove the Service and Driver now.'+cNL+
'Press [Cancel] for no change.',
mtInformation, [mbYes, mbNo, mbCancel], 0)
else ModalResult:=MessageDlg(Status+
'This was done for permanence. Current process will keep it.'+cNL+
'Press [No] to stop and remove the Service and Driver now.'+cNL+
'Press [OK] to lift permanence. Removes driver after Close/Exit. '+cNL+
'Press [Cancel] for no change.',
mtInformation, [mbNo, mbOk, mbCancel], 0);
case ModalResult of
mrYes:
begin
// Ensure that PortTalk will neither stop nor remove the
// Service when closing.
fSrvStarted:=False;
fSrvInstalled:=False;
// Report flagging change..
Report('Flagging to keep resident after Close/Exit.');
end;
mrNo:
begin
// Close PortTalk now after setting the flags that will
// cause complete removal
fSrvStarted:=True;
fSrvInstalled:=True;
Close;
end;
mrOk:
begin
// Ensure that PortTalk will later stop and remove the
// Service and driver when closing.
fSrvStarted:=True;
fSrvInstalled:=True;
// Report flagging change..
Report('Flagging to stop/remove after Close/Exit.');
end;
end;
end else
begin
// WinNT - WinNTAdmin - PortTalk NOT Active. Driver and Service
// may or may not be present. PortTalk may or may not previously
// have been opened. The internal flags cannot be trusted to
// contain information. We can offer to ensure (de-)installation.
// Note that we *could* look in
// HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services
// to see whether or not the service is present..
ModalResult:=MessageDlg(Status+
'PortTalk is currently NOT Active.'+cNL+
'Presence of Driver and Service is not determined.'+cNL+
'Press [Yes] to activate and ensure permanent installation.'+cNL+
'Press [No] to ensure stop and removal of Service and Driver.'+cNL+
'Press [OK] to activate/install for the current process.'+cNL+
'Press [Cancel] for no change.',
mtInformation, [mbYes, mbNo, mbOk, mbCancel], 0);
case ModalResult of
mrYes:
begin
// Open PortTalk (also for the current process) and immediately
// clear the flag that we have installed (if so) to keep the
// installation present after next Close/Exit.
Open;
fSrvStarted:=False;
fSrvInstalled:=False;
end;
mrNo:
begin
// Remove the Service and Driver
StopPTService(True);
end;
mrOk:
begin
// Open PortTalk (also for the current process) in standard way.
// Automatically Stops/Removes at next Close/Exit.
Open;
end;
end;
end;
// Report that this procedure has ended..
Report('< Admin End >');
end;
end.