The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
//////////////////////////////////////////////////////////////////////////////
//
//  Daemon.cpp
//  Win32::Daemon Perl extension main source file
//
//  Copyright (c) 1998-2008 Dave Roth
//  Courtesy of Roth Consulting
//  http://www.roth.net/
//
//  This file may be copied or modified only under the terms of either 
//  the Artistic License or the GNU General Public License, which may 
//  be found in the Perl 5.0 source kit.
//
//  2008.03.24  :Date
//  20080324    :Version
//////////////////////////////////////////////////////////////////////////////


// Enable Win32::Daemon callback support
#define ENABLE_CALLBACKS

// Enable MS Visual Studio 2005's secure stdlib
// The _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES macro defined with a value of 1
// will swap out a secured version of stdlib functions. These perform buffer overrun
// checking and such.
//	define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES	1

//	As an alternative you can keep the depricated function calls and block out any
//	security warnings by defining the _CRT_SECURE_NO_DEPRECATE macro.
#define _CRT_SECURE_NO_DEPRECATE



#define WIN32_LEAN_AND_MEAN

#ifdef __BORLANDC__
typedef wchar_t wctype_t; /* in tchar.h, but unavailable unless _UNICODE */
#endif

#include <windows.h>
#include <tchar.h>
#include <wtypes.h>
#include <stdio.h>      //  Gurusamy's right, Borland is brain damaged!
#include <math.h>       //  Gurusamy's right, MS is brain damaged!
#include <time.h>

//  Use headers that define the security stuff
#include <lmaccess.h>
#include <lmserver.h>
#include <lmapibuf.h>
#include <LMERR.H>      //  For the NERR_Succes macro

#include "XS_Win32Perl.h"
//	#include <preWin32Perl.h>
//	#include <Win32Perl.h>

#include "constant.h"
#include "CWinStation.hpp"
#include "ServiceThread.hpp"

#ifdef ENABLE_CALLBACKS
	#include "CCallbackList.hpp"
	#include "CCallbackTimer.hpp"
#endif // ENABLE_CALLBACKS
#include "daemon.h"

#define SET_SERVICE_BITS_LIBRARY    TEXT( "AdvApi32.dll" )
#define SET_SERVICE_BITS_FUNCTION   TEXT( "SetServiceBits" )


/*----------------------- M I S C   F U N C T I O N S -------------------*/
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int My_SetServiceBits( SERVICE_STATUS_HANDLE hService, DWORD dwServiceBits, BOOL bSetBitsOn, BOOL bUpdateImmediately )
{
    int iResult = 0;
    HINSTANCE hInstance;
    DWORD dwMask = USER_SERVICE_BITS_MASK;      // Default mask. These bits are not reserved by MS. We can use these w/o problems w/MS products.

    hInstance = LoadLibrary( SET_SERVICE_BITS_LIBRARY );
    if( NULL != hInstance )
    {
        typedef BOOL (CALLBACK *fSetServiceBits) ( SERVICE_STATUS_HANDLE hServiceGoo, DWORD dwBits, BOOL bBitsOn, BOOL bUpdate );
        fSetServiceBits pSetServiceBits = NULL;
        pSetServiceBits= (fSetServiceBits) GetProcAddress( hInstance, SET_SERVICE_BITS_FUNCTION );
        if( NULL != pSetServiceBits )
        {
            // Note:
            // We clear all user defined bits before applying our bits. If this sucks
            // then use something like Win32::Lanman. We only want to track our bits
            // and not mess with others.

            //  First clear all user defined bits...
            // My_SetServiceBits() will mask out the user bits from the passed in DWORD.
            (*pSetServiceBits)( ghService, 0xFFFFFFFF, FALSE, FALSE );

            // Mask those lovely bits!
            dwServiceBits &= dwMask;
            iResult = (*pSetServiceBits)( hService, dwServiceBits, bSetBitsOn, bUpdateImmediately );
        }
    }

    return( iResult );
}

BOOL GetProcessSid( HANDLE hProcess, SID *pSid, DWORD dwSidBufferSize )
{
    BOOL fResult = FALSE;
    HANDLE hToken = NULL;

    if( ( NULL == pSid ) || ( sizeof( SID ) > dwSidBufferSize ) )
    {
        return( FALSE );
    }

    // Manually get the process token since we need it for more than just
    // calling into SetPrivilege()...
    if( OpenProcessToken(
            hProcess,
            TOKEN_ADJUST_PRIVILEGES
            | TOKEN_QUERY
            | TOKEN_QUERY_SOURCE,
            &hToken ) )
    {
        // Now that you have the token for this process, you want to set
        // the SE_DEBUG_NAME privilege.

// TODO:
// We may want to not do this here. Only if we show the
// service. Otherwise this may give us too many privileges that
// we may not want to have

        SetPrivilege( hToken, SE_DEBUG_NAME, TRUE );

        // Determine the SID for the current process. We need
        // to give this SID permissions to access the
        // desktop object...
        fResult =  GetSidFromToken( hToken, pSid, dwSidBufferSize );
        CloseHandle( hToken );
    }
    return( fResult );
}


////////////////////////////////////////////////////////////////////////////
//  ResetCallbackTimer()
//  Stops or changes the callback timer.
//  Passing in nothing simply stops/starts the timer (depending upon the state)
//  Passing in 0 stops the timer.
//  Passing in any positive value starts the timer and sets that value as callback timeout.
//  
//	Default pass in value is -1 (if nothing is specified).
//
/*
BOOL ResetCallbackTimer( UINT uintTimeoutValue )
{
    //
    // If the callback timer is active then kill it
    //
    if( 0 != gpuintCallbackTimerID )
    {
        KillTimer( NULL, gpuintCallbackTimerID );
        gpuintCallbackTimerID = 0;
        if( -1 == uintTimeoutValue )
        {
            return( TRUE );
        }
    }

    //
    //  Are we changing the value? A 0 means to simply stop
    //  the timer. Otherwise change the timeout value...
    //
    if( 0 == uintTimeoutValue ) return( TRUE );

    if( 0 <= (int) uintTimeoutValue )
    {
        guintCallbackTimer = uintTimeoutValue;
    }
        
    if( 0 != guintCallbackTimer )
    {
        gpuintCallbackTimerID = SetTimer( NULL, CALLBACK_RUNNING_TIMER, guintCallbackTimer, (TIMERPROC) NULL );
    }
    
    return( TRUE );
}
*/

////////////////////////////////////////////////////////////////////////////
//  Fill in a SID buffer from the specified token
BOOL GetSidFromToken( HANDLE hToken, SID *pSid, DWORD dwSidBufferSize )
{
    BOOL fResult = FALSE;

    if( NULL != hToken )
    {
        PTOKEN_USER  pTokenUserStruct = NULL;
        DWORD dwLength = 0;

        if( FALSE == GetTokenInformation(
                hToken,
                TokenUser,
                pTokenUserStruct,
                0,
                &dwLength ) )
        {
            pTokenUserStruct = (PTOKEN_USER) new BYTE [ dwLength ];
            if( NULL != pTokenUserStruct )
            {
                ZeroMemory( pTokenUserStruct, dwLength );
                if( GetTokenInformation(
                        hToken,
                        TokenUser,
                        pTokenUserStruct,
                        dwLength,
                        &dwLength ) )
                {
                    if( FALSE != IsValidSid( pTokenUserStruct->User.Sid ) )
                    {
                        DWORD dwSidLength;

                        dwSidLength = GetLengthSid( pTokenUserStruct->User.Sid );
                        if( dwSidLength <= dwSidBufferSize )
                        {
                            fResult = CopySid( dwSidLength, pSid, pTokenUserStruct->User.Sid );
                        }
                    }
                }
                delete [] pTokenUserStruct;
            }
        }
    }
    return( fResult );
}

////////////////////////////////////////////////////////////////////////////
// The SetPrivilege function will accept a handle to a token, a
// privilege, and a flag to either enable/disable that privilege. The
// function will attempt to perform the desired action upon the token
// returning TRUE if it succeeded, or FALSE if it failed.
BOOL SetPrivilege( HANDLE hToken, const char *pszPrivilege, BOOL bSetFlag )
{
    TOKEN_PRIVILEGES structPriv, structPrevPriv;
    LUID Luid;
    DWORD dwStructSize = sizeof( structPrevPriv );
    BOOL fResult = FALSE;
    BOOL fCloseToken = FALSE;

    // If no token is specified assume that the process token is desired.
    if( 0 == hToken )
    {    
        OpenProcessToken(
            GetCurrentProcess(),
            TOKEN_ADJUST_PRIVILEGES
            | TOKEN_QUERY
            | TOKEN_QUERY_SOURCE,
            &hToken );
        fCloseToken = TRUE;
    }
    if( 0 != hToken )
    {
        ZeroMemory( &structPrevPriv, sizeof( structPrevPriv ) );
        ZeroMemory( &structPriv, sizeof( structPriv ) );

        // Grab the LUID for the request privilege.
        if( LookupPrivilegeValue( "", pszPrivilege, &Luid ) )
        {
            // Set up basic information for a call.
            // You want to retrieve the current privileges
            // of the token under concern before you can modify them.
            structPriv.PrivilegeCount = 1;
            structPriv.Privileges[0].Luid = Luid;
            structPriv.Privileges[0].Attributes = ( bSetFlag )? SE_PRIVILEGE_ENABLED : 0;


            // You need to acquire the current privileges first
            fResult = ( FALSE != AdjustTokenPrivileges(
                                    hToken,
                                    0,
                                    &structPriv,
                                    sizeof( structPriv ),
                                    &structPrevPriv,
                                    &dwStructSize ) );
        }
        if( FALSE != fCloseToken )
        {
            CloseHandle( hToken );
        }
    }
    return( fResult );
}

////////////////////////////////////////////////////////////////////////////
//  Fill the specified text buffer with the text representation of the
//  specified SID.
//  NOTE: The buffer must have enough memory to represent the SID.
void TextFromSid( LPTSTR pszBuffer, SID *pSid )
{
    TCHAR szTemp[ 100 ];
    int iCount;
    PUCHAR puCount;

    SID_IDENTIFIER_AUTHORITY *pSia;

    if( FALSE == IsValidSid( pSid ) )
    {
        return;
    }

    pSia = GetSidIdentifierAuthority( pSid );

    //  Create the string version of the SID
    //  We begin by S (for SID) - the revision level - the authority
    //  identifier. Note that we are assuming that the Authority ID is in
    //  the sixth element (element #5 starting at 0) in Authority Identifier
    //  structure. Currently as of this writing SID strings do not display
    //  the other values in the pSiz strucutre. They are normally something
    //  like: {0,0,0,0,0,5} where the last byte contains a value and the others
    //  are zero.
    iCount = wsprintf( pszBuffer, TEXT( "S-%d-%d" ), pSid->Revision, (DWORD) pSia->Value[5] );
    puCount = GetSidSubAuthorityCount( pSid );
    for( DWORD dwTemp = 0; dwTemp < *puCount; dwTemp++ )
    {
        wsprintf( szTemp, TEXT( "-%d" ), (DWORD) *( GetSidSubAuthority( pSid, dwTemp ) ) );
        _tcscat( pszBuffer, szTemp );
    }
}

////////////////////////////////////////////////////////////////////////////
//  Load the user's profile hive into the HKEY_USERS Registry key.
BOOL LoadProfile( SID *pSid )
{
    BOOL fResult = FALSE;
    TCHAR szUserProfilePath[ 1024 ];
    TCHAR szSid[ 64 ];
    HKEY hKey = NULL;
#ifdef DEBUG
    TCHAR szDebugText[ 1024 ];
#endif // DEBUG

    ZeroMemory( szSid, sizeof( szSid ) );
    TextFromSid( szSid, pSid );

    _tcscpy( szUserProfilePath, REG_KEY_USER_LOCAL_PROFILE );
    _tcscat( szUserProfilePath, szSid );

#ifdef DEBUG
    wsprintf( szDebugText, TEXT( "[LoadProfile] Loading profile for %s" ), szSid );
    ALERT( szDebugText );
#endif

    // Check to see if we already have the profile loaded
    if( ERROR_SUCCESS == RegOpenKeyEx( HKEY_USERS,
                                        szSid,
                                        0,
                                        KEY_READ,
                                        &hKey ) )
    {
        RegCloseKey( hKey );
        ALERT( "[LoadProfile] Profile already loaded." );
        return( TRUE );
    }

    // If we got here then the profile is not loaded, let's load it.
    // First open the Registry key where profile info is held
    if( ERROR_SUCCESS == RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                                        szUserProfilePath,
                                        0,
                                        KEY_READ,
                                        &hKey ) )
    {
        TCHAR szCentralPath[ MAX_PATH + 1 ];
        TCHAR szLocalPath[ MAX_PATH + 1 ];
        DWORD dwLength;
        DWORD dwType;
        HANDLE hToken = NULL;

        _tcscpy( szCentralPath, TEXT( "" ) );
        _tcscpy( szLocalPath, TEXT( "" ) );

        // We need to be granted Restore Name privileges. Otherwise
        // we won't be able to load the profile
        if( OpenProcessToken(
                GetCurrentProcess(),
                TOKEN_ADJUST_PRIVILEGES
                | TOKEN_QUERY
                | TOKEN_QUERY_SOURCE,
                &hToken ) )
        {
            // Now that you have the token for this process, you want to set
            // the SE_RESTORE_NAME privilege.
            SetPrivilege( hToken, SE_RESTORE_NAME, TRUE );
        }

        dwLength = sizeof( szCentralPath );
        if( ERROR_SUCCESS == RegQueryValueEx(
                                hKey,
                                REG_VALUE_USER_CENTRAL_PROFILE_PATH,
                                0,
                                &dwType,
                                (LPBYTE) szCentralPath,
                                &dwLength
                                ) )
        {
            // Expand environment variables if needed
            if( REG_EXPAND_SZ == dwType )
            {
                TCHAR szBuffer[ MAX_PATH + 1 ];

                _tcscpy( szBuffer, szCentralPath );
                ExpandEnvironmentStrings( szBuffer, szCentralPath, sizeof( szCentralPath ) );
            }
        }

        dwLength = sizeof( szLocalPath );
        if( ERROR_SUCCESS == RegQueryValueEx(
                                hKey,
                                REG_VALUE_USER_LOCAL_PROFILE_PATH,
                                0,
                                &dwType,
                                (LPBYTE) szLocalPath,
                                &dwLength
                                ) )
        {
            // Expand environment variables if needed
            if( REG_EXPAND_SZ == dwType )
            {
                TCHAR szBuffer[ MAX_PATH + 1 ];

                _tcscpy( szBuffer, szLocalPath );
                ExpandEnvironmentStrings( szBuffer, szLocalPath, sizeof( szLocalPath ) );
            }
        }

        if( 0 != _tcscmp( szCentralPath, TEXT( "" ) ) )
        {
            TCHAR szProfilePath[ MAX_PATH ];

            _tcscpy( szProfilePath, szCentralPath );
            _tcscat( szProfilePath, TEXT( "\\" ) );
            _tcscat( szProfilePath, USER_PROFILE_HIVE_NAME );

            // Try to load the profile. This may fail if the
            // path is a UNC to another machine or for other
            // reasons. RegLoadKey() seems to only load local
            // copies of profiles--possibly to prevent corruption
            // if the net goes down.
            // If this is the case we may want to copy the file
            // over.
            if( ERROR_SUCCESS == RegLoadKey(
                                            HKEY_USERS,
                                            szSid,
                                            szProfilePath
                                            ) )
            {
                fResult = TRUE;
#ifdef DEBUG
                wsprintf( szDebugText, TEXT( "[LoadProfile] Loaded %s" ), szProfilePath );
                ALERT( szDebugText );
#endif
            }
            else if( 0 != _tcscmp( szLocalPath, TEXT( "" ) ) )
            {
                TCHAR szDestination[ MAX_PATH ];

                _tcscpy( szDestination, szLocalPath );

                // determine if it is a file or directory, WinNT 4.0 it is a
                // directory
                // WinNT 3.51 it is a file
                //
                if( FILE_ATTRIBUTE_DIRECTORY == ( GetFileAttributes( szProfilePath )
                                                  & FILE_ATTRIBUTE_DIRECTORY ) )
                {
                    _tcscat( szProfilePath, TEXT( "\\" ) );
                    _tcscat( szProfilePath, USER_PROFILE_HIVE_NAME );
                }


                // Copy the profile hive file from the origin location to the
                // local local cached profile
                CopyFile( szProfilePath, szDestination, TRUE );
#ifdef DEBUG
                wsprintf( szDebugText, TEXT( "[LoadProfile] Copied path from %s to %s" ), szProfilePath, szDestination );
                ALERT( szDebugText );
#endif

            }
        }

        // Were we successful?
        if( ( FALSE == fResult ) && ( 0 != _tcscmp( szLocalPath, TEXT( "" ) ) ) )
        {
            // No, try loading the local path.
            TCHAR szProfilePath[ MAX_PATH ];

            _tcscpy( szProfilePath, szLocalPath );


            // determine if it is a file or directory, WinNT 4.0 it is a
            // directory
            // WinNT 3.51 it is a file
            //
            if( FILE_ATTRIBUTE_DIRECTORY == ( GetFileAttributes( szProfilePath )
                                              & FILE_ATTRIBUTE_DIRECTORY ) )
            {
                _tcscat( szProfilePath, TEXT( "\\" ) );
                _tcscat( szProfilePath, USER_PROFILE_HIVE_NAME );
            }

            // Try to load the profile.
            if( ERROR_SUCCESS == RegLoadKey(
                                            HKEY_USERS,
                                            szSid,
                                            szProfilePath
                                            ) )
            {
                fResult = TRUE;
#ifdef DEBUG
                wsprintf( szDebugText, TEXT( "[LoadProfile] Loaded %s" ), szProfilePath );
                ALERT( szDebugText );
#endif
            }
        }

        RegCloseKey( hKey );
    }

    return( fResult );
}

//////////////////////////////////////////////////////////////////
//  Store the service's description in the Registry. Win2k does
//  this for you but we need to be backward compatible.
BOOL StoreServiceDescription( LPCTSTR pszMachine, LPCTSTR pszServiceName, LPCTSTR pszDescription )
{
    HKEY hRoot = HKEY_LOCAL_MACHINE;
    HKEY hKey = NULL;
    BOOL fFlag = TRUE;
    BOOL fResult = FALSE;

    if( 0 != _tcscmp( TEXT( "" ), pszMachine ) )
    {
        fFlag = ( ERROR_SUCCESS == RegConnectRegistry( pszMachine, HKEY_LOCAL_MACHINE, &hRoot ) );
    }

    if( TRUE == fFlag )
    {
        TCHAR szBuffer[ 75 ];
        _stprintf( szBuffer, REGISTRY_SERVICE_PATH TEXT( "\\%s" ), pszServiceName );
        if( ERROR_SUCCESS == RegOpenKeyEx( hRoot, szBuffer, 0, KEY_SET_VALUE, &hKey ) )
        {
            DWORD dwSize = (DWORD)_tcslen( pszDescription ) * sizeof( TCHAR );
            fResult = ( ERROR_SUCCESS == RegSetValueEx( hKey, REGISTRY_SERVICE_KEYWORD_DESCRIPTION, 0, REG_SZ, (LPBYTE) pszDescription, dwSize ) );
        }
        RegCloseKey( hKey );
    }

    if( 0 != _tcscmp( TEXT( "" ), pszMachine ) )
    {
        RegCloseKey( hRoot );
    }

    return( fResult );
}


#ifdef ENABLE_CALLBACKS
//////////////////////////////////////////////////////////////////
//  The idea here is that when a state change occurs because the
//	SCM has submitted a command (stop, pause, start, interrogate, etc)
//	we can call this function. If callbacks have been enabled then 
//	this function will execute the Perl callback if a callback routine 
//	has been provided. Otherwise we just pass through.
//  
BOOL ProcessStateChange( pTHX_ DWORD dwCommand, HV* pHvContext )
{
	BOOL fMakeContextHash = FALSE;
	PVOID pSvSubroutine = gCallback.Get( dwCommand );
	
	//
	// The SERVICE_CONTROL_TIMER has replaced SERVICE_RUNNING. Since
	// SERVICE_RUNNING is easily confused with another constant with teh
	// same value we have renamed this SERVICE_CONTROL_RUNNING for clarity.
	// Regardless, SERVICE_CONTROL_RUNNING is being depreciated and replaced
	// with SERVICE_CONTROL_TIMER to better indicate why the callback is 
	// occuring.
	// For legacy purposes:
	//	1) Registering a "running" callback will still work
	//	2) Registering a "timer" callback is preferred instead of "running"
	//	3) Registering both will result in only a "timer" callback
	//
	// Now let's check if this is the timer command AND if the script has
	// registered only the "running" callback...
	if( SERVICE_CONTROL_TIMER == dwCommand )
	{
//
//	Code currently changes 3) to result in "running" for legacy support
//	if code called Callback() passing in only one catchall subroutine reference
//

		PVOID pSvTemp;
		pSvTemp = gCallback.Get( SERVICE_CONTROL_RUNNING );
		if( NULL != pSvTemp )
		{
			pSvSubroutine = pSvTemp;
			dwCommand = SERVICE_CONTROL_RUNNING;
		}

     }	

#ifdef aTHX
    if( NULL == aTHX )
    {
        return( FALSE );
    }
#endif // aTHX
    
	if( NULL == pSvSubroutine )
	{
		return( FALSE );
	}

	ALERT( "ProcessStateChange: checking for context hash..." );
	// If there is no context hash provided then make one...
	if( NULL == pHvContext )
	{
		ALERT( "ProcessStateChange: Creating a new context hash" );
		pHvContext = newHV();
	}
	else
	{
		// If we don't create the context HV then increase its ref count because
		// later we will decrease it. If we created the HV then the decrease will
		// auto destroy it.
		ALERT( "ProcessStateChange: Increasing ref count on existing context hash" );
		SvREFCNT_inc( (SV*) pHvContext );
	}

	 ALERT( "ProcessStateChange: Storing value into context hash" );
	HASH_STORE_IV( pHvContext, KEYWORD_CALLBACK_COMMAND_NAME, dwCommand );

	ALERT( "ProcessStateChange: Checking for a valid subroutine" );
	if( SVt_PVCV == SvTYPE( (SV*) pSvSubroutine ) )
	{
		ALERT( "About to call into a Perl routine..." );
		//	Call into the Perl subroutine
		//  Push onto the stack the hash context
		CallPerlRoutine( aTHX_ (CV*) pSvSubroutine, 
						dwCommand,
						pHvContext );
		ALERT( "Back from calling into a Perl routine..." );
		//	Callback into the subroutine.
		//	The subroutine should look like:
		//
		//	sub EventCallback
		//	{
		//	  my( $Event, $Context ) = @_;
		//		# Process the event
		//		Win32::Daemon::State( $NewState );		
		//	  return;
		//	}
		//
		//	=================== OR ====================
		//	sub EventCallback
		//	{
		//	  my( $Event, $Context ) = @_;
		//		# Process the event
		//	  return( $NewState );
		//	}

	}
	
	// Decrease the context HV reference counter. If we created the HV then it is
	// unloaded at this time. Otherwise we have previously increased the count so
	// this just decreases it back to where it started at the beginning of this
	// routine.
	SvREFCNT_dec( (SV*) pHvContext );
	return( TRUE );
}
#endif // ENABLE_CALLBACKS

#ifdef ENABLE_CALLBACKS
////////////////////////////////////////////////////////////////////////////
//  CallPerlRoutine()
//  This will callback into a specified Perl subroutine passing in any
//  SVs that are passed into the function.
//  Nothing is returned.
//
//  void CallPerlRoutine( pTHX_ CV* pPerlSubroutine, int iTotalParams, ... )
void CallPerlRoutine( pTHX_ CV* pPerlSubroutine, DWORD dwCommand, HV* pHvContext )
{
    SV *pSv = NULL;
    int iReturnedItems = 0;

	// Make sure that a routine was passed in AND that it is really a code reference
    if( ( NULL == pPerlSubroutine ) || ( SVt_PVCV != SvTYPE( (SV*) pPerlSubroutine ) ) )
    {
        return;
    }

    //  Declare necessary vars...
    dSP;

    //  Begin a new scope...
    ENTER;

    //  Start mortal stack. All mortals after this point will be put onto this stack
    //  which will be freeded up later with FREETMPS
    SAVETMPS;

    //  Remember (or push) the current position of the stack...
    ALERT( "CallPerlRoutine: Pushing parameters onto the perl stack" );
    PUSHMARK( sp );
    XPUSHs( sv_2mortal( newSViv( (IV) dwCommand ) ) );
    XPUSHs( (SV*) newRV_inc( (SV*) pHvContext ) );
    //  Mark the end of arguments on the stack...
    PUTBACK;
    ALERT( "CallPerlRoutine: Done: Pushed parameters onto the perl stack" );

	TCHAR szBuff[ 256 ];
    sprintf( szBuff, "CallPerlRoutine: Calling into Perl for command: 0x%04x.\n", dwCommand );
    ALERT( szBuff );

    ALERT( "CallPerlRoutine: Calling into the perl routine now..." );

	iReturnedItems = perl_call_sv( (SV*) pPerlSubroutine, G_SCALAR );
    
	ALERT( "CallPerlRoutine: ...Back from calling the perl routine." );
    
    //  Begin the process of unwinding the return stack...
    SPAGAIN;

	if( iReturnedItems )
	{
		SV *pSvReturn = POPs;

		//
		//	Check if the return value is an integer
		//
		if( SvIOK( pSvReturn ) )
		{
			//
			//	Any integer return value is a new state value
			//	so update the service's state. Specify waithint value of 0
			//	and error value of 0xFFFFFFFF to use defaults.
			//
                        DWORD dwNewState = (DWORD)SvIV( pSvReturn );
			UpdateServiceStatus( dwNewState, 0, 0xFFFFFFFF );
		}

		//
		//	NOTE: the while() look predecriments iReturnedItems to 
		//	accomodate the new state value taken off of the stack
		//
		while( --iReturnedItems )
		{
			//  Pop an SV off of the return stack...
			POPs;
		}
	}

    //  We are done here so put back the stack pointer...
    PUTBACK;

    //  Unwind and destroy any mortals that are on our temp stack...
    FREETMPS;

    //  Leave our little scope...
    LEAVE;
}
#endif // ENABLE_CALLBACKS


////////////////////////////////////////////////////////////////////////////
//  DispatchThreadMessage()
//  This is called for every message that the thread message queue receives.
//	This is used instead of DispatchMessage() since there is no window available.
//
void DispatchThreadMessage( MSG *pMsg )
{
	// Retrieve the Perl context...
	dTHX;

#ifdef _DEBUG
    TCHAR szBuffer[ 1024 ];
    wsprintf( szBuffer, TEXT( "Servicing Thread Message Queue: Message = 0x%04x; aTHX = 0x%04x." ), pMsg->message, aTHX );
    ALERT( szBuffer );
#endif // _DEBUG

	BOOL fCallbackState = FALSE;

	switch( pMsg->message )
	{
        case WM_DAEMON_STATE_CHANGE:
		// We have a state change!
		// Notice we are using gPerlObject here! Therefore we MUST be in 
		// callback mode!
		//
		//  First reset the timer. Don't pass in any value so that it just
		//  temporarily pauses the timer. The next time you call it without
		//  any params it will start it again. This prevents queuing up 
		//  timeout messages if the callback takes time. 
		ALERT( TEXT( "...processing WM_DAEMON_STATE_CHANGE message\n" ) )
		fCallbackState =  gCallbackTimer.QueryState();
		if( fCallbackState )
		{
			gCallbackTimer.Stop();
		}
		ProcessStateChange( aTHX_ (DWORD) pMsg->wParam, gpHvContext );
		if( fCallbackState )
		{
			gCallbackTimer.Start();
		}
		break;
        
	case WM_TIMER:
		//
		//  You get here when the callback timeout value has been exceeded. This
		//  simply means that it is time to callback into the Perl script to give
		//  the script a chance to process anything it needs to.
		//  The script sees this event as a "SERVICE_RUNNING" event.
		//
			//
		//  First reset the timer. Don't pass in any value so that it just
		//  temporarily pauses the timer. The next time you call it without
		//  any params it will start it again. This prevents queuing up 
		//  timeout messages if the callback takes time. 
		ALERT( TEXT( "...processing WM_TIMER message (heartbeat callback)\n" ) )
		fCallbackState =  gCallbackTimer.QueryState();
		if( fCallbackState )
		{
			gCallbackTimer.Stop();
		}
		ProcessStateChange( aTHX_ (DWORD) SERVICE_CONTROL_TIMER, gpHvContext );
		if( fCallbackState )
		{
			gCallbackTimer.Start();
		}
		break;
		
	default:
		ALERT( "...Default handler has been invoked." );
	
    }
}

////////////////////////////////////////////////////////////////////////////
//  TimerProc()
//  This is called by a Win32 Timer every time the timer's timeout value is
//  reached. This is used to determine when to callback into the Perl script
//  to allow it for processing. When the callback into Perl occurs it does 
//  so indicating the SERVICE_RUNNING state.
//
void CALLBACK TimerProc(
   HWND hWnd,      // handle of CWnd that called SetTimer
   UINT nMsg,      // WM_TIMER
   UINT nIDEvent,   // timer identification
   DWORD dwTime    // system time
)
{
    MSG sMsg;

    ZeroMemory( &sMsg, sizeof( sMsg ) );
    sMsg.hwnd = hWnd;
    sMsg.lParam = 0;
    sMsg.message = WM_TIMER;
//    sMsg.pt = 0;
    sMsg.time = dwTime;
    sMsg.wParam = nIDEvent;

    DispatchThreadMessage( &sMsg );
}


#ifdef _DEBUG
//////////////////////////////////////////////////////////////////
//
//
HANDLE CreateLog( LPCTSTR pszPath )
{
    HANDLE hFile = 0;

    ZeroMemory( gszDebugOutputPath, sizeof( gszDebugOutputPath ) );
    _tcsncpy( gszDebugOutputPath, pszPath, sizeof( gszDebugOutputPath ) - 1 );

    if( 0 != ghLogFile )
    {
        CloseHandle( ghLogFile );
        ghLogFile = 0;
    }

    if( _tcscmp( TEXT( "" ), gszDebugOutputPath ) != 0 )
    {
        ghLogFile = CreateFile( gszDebugOutputPath,
                                GENERIC_READ | GENERIC_WRITE,
                                FILE_SHARE_READ,
                                NULL,
                                CREATE_ALWAYS,
                                FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
                                NULL );
        if( 0 == ghLogFile )
        {
            _tcscpy( gszDebugOutputPath, TEXT( "" ) );
        }
    }
    return( ghLogFile );
}
#endif // _DEBUG


/* ===============  DLL Specific  Functions  ===================  */
//////////////////////////////////////////////////////////////////
#if defined(__cplusplus)
extern "C"
#endif
BOOL WINAPI DllMain( HINSTANCE  hinstDLL, DWORD fdwReason, LPVOID  lpvReserved )
{
    BOOL    fResult = TRUE;
    DWORD	dwSidBuferSize = MAX_SID_SIZE;

    // Fetch the OS version number...
    ZeroMemory( &gsOSVerInfo, sizeof( gsOSVerInfo ) );
    gsOSVerInfo.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );
    GetVersionEx( &gsOSVerInfo );
    
    switch( fdwReason )
    {
        case DLL_PROCESS_ATTACH:

            ghDLL = hinstDLL;
            CountConstants();

#ifdef ENABLE_CALLBACKS            
            gfCallbackMode = FALSE;
//            gpPerlObject = NULL;
            gpHvContext = NULL;
#endif // ENABLE_CALLBACKS            
  
            gdwLastError = 0;
            gdwServiceErrorState = NO_ERROR;
            ghService = 0;
            ghServiceThread = 0;
            gServiceThreadId = 0;
			gServiceMainThreadID = 0;

#ifdef ENABLE_CALLBACKS
			//
			//	Setup the callback timer
			//
			gCallbackTimer.SetMessageID( CALLBACK_TIMER_ID );
			gCallbackTimer = DEFAULT_CALLBACK_TIMER;
#endif	// ENABLE_CALLBACKS			

			gMainThreadId = GetCurrentThreadId();
			gdwLastControlMessage = SERVICE_CONTROL_NONE;
			gdwTimeoutState = SERVICE_START_PENDING;
			
			gdwControlsAccepted = 0;
			switch( gsOSVerInfo.dwMajorVersion )
			{
				case 6:
					// We have Windows Vista
					//  The following constants only work on Vista and higher:
					//      SERVICE_ACCEPT_PRESHUTDOWN
					//
#ifdef SERVICE_CONTROL_PRESHUTDOWN 					
					gdwControlsAccepted |= SERVICE_ACCEPT_PRESHUTDOWN;
#endif	// SERVICE_CONTROL_PRESHUTDOWN 			

				case 5:
					// We have Windows 2000 or XP
					//  The following constants only work on Win2k and higher:
					//      SERVICE_ACCEPT_PARAMCHANGE
					//      SERVICE_ACCEPT_NETBINDCHANGE
					//
					gdwControlsAccepted |= SERVICE_ACCEPT_PARAMCHANGE  
										   | SERVICE_ACCEPT_NETBINDCHANGE;
										
            	case 4:
				case 3:
				case 2:
				case 1:
				case 0:
					// NT 4.0
					gdwControlsAccepted |= SERVICE_ACCEPT_STOP 
										  | SERVICE_ACCEPT_PAUSE_CONTINUE
										  | SERVICE_ACCEPT_SHUTDOWN;

			}
			gdwServiceType = SERVICE_WIN32_OWN_PROCESS 
                             | SERVICE_INTERACTIVE_PROCESS;
            gdwServiceBits = 0;
            gdwLastError = 0;
            gpSid = NULL;


            // Set the state to 0. This way we know when the service thread actually
            // starts because it will set the state to SERVICE_START_PENDING
            gdwState = 0;
            ZeroMemory( &gServiceStatus, sizeof( gServiceStatus ) );

            if( ! GetModuleFileName( ghDLL, gszModulePath, sizeof( gszModulePath ) ) )
            {
                _tcscpy( gszModulePath, TEXT( "" ) );
            }

#ifdef _DEBUG
            // Init a critical section for debug output. This way multiple threads
            // won't overrun the output code.
            ZeroMemory( &gcsDebugOutput, sizeof( gcsDebugOutput ) );
            InitializeCriticalSection( &gcsDebugOutput );
            
            // Clear out the log file path but DONT create the log. Leave that
            // to the Perl script.
            ZeroMemory( gszDebugOutputPath, sizeof( gszDebugOutputPath ) );

#endif // _DEBUG

            if( NULL != ( gpSid = (SID*) new BYTE [ dwSidBuferSize ] ) )
            {
                // Here we need to get the SID of the user account
                // we are running under
                ZeroMemory( gpSid, dwSidBuferSize );
                if( FALSE != GetProcessSid( GetCurrentProcess(), gpSid, dwSidBuferSize ) )
                {
                    gWindowStation.SetSid( gpSid );
                    LoadProfile( gpSid );
                }
            }
            break;

        case DLL_THREAD_ATTACH:
            giThread++;
            break;

        case DLL_THREAD_DETACH:
            giThread--;
            break;

        case DLL_PROCESS_DETACH:
#ifdef _DEBUG
            DeleteCriticalSection( &gcsDebugOutput );
            if( ghLogFile )
            {
                CloseHandle( ghLogFile );
            }
#endif // _DEBUG
            break;

        default:
            break;
    }
    return ( fResult );
}

/*----------------------- P E R L   F U N C T I O N S -------------------*/
//////////////////////////////////////////////////////////////////

MODULE = Win32::Daemon  PACKAGE = Win32::Daemon

PROTOTYPES: DISABLE

# INCLUDE: \include\XS_RothMacros.xsh

int
Constant( pszName, pSvBuffer )
    LPTSTR	pszName
    SV*		pSvBuffer

	PREINIT:
	eConstantType eResult;
	LPVOID	pBuffer = NULL;

	CODE:
		eResult = Constant( pszName, &pBuffer );
		switch( eResult )
		{
			case String:
				sv_setpv( pSvBuffer, (LPTSTR) pBuffer );
				break;

			case Numeric:
				sv_setiv( pSvBuffer, (IV) pBuffer );
				break;
		}

		//  Return the result type.
		RETVAL = eResult;
	
	OUTPUT:
		RETVAL
		pSvBuffer

int
RegisterCallbacks( pSv )
	SV*	pSv

	CODE:
	{
		//////////////////////////////////////////////////////////////////
		//  This Perl routine must pass in either a code reference or a
		//  hash reference with callback code references for each event. 
		//
		RETVAL = TRUE;
#ifndef ENABLE_CALLBACKS
	croak( "RegisterCallbacks() is not supported in this build. Define the ENABLE_CALLBACKS macro and recompile\n" );
#else
		if( 1 != items )
		{
			croak( "Usage: " EXTENSION "::RegisterCallbacks( $SubRef | $HashRef )\n" );			
		}
		
		//
		// If we have a reference then de-ref it
		//
		if( SvROK( pSv ) )
		{
			pSv = SvRV( pSv );
		}

		// Check for a subroutine reference
		if( SVt_PVCV == SvTYPE( pSv ) )
		{
			//
			// Passed in a subroutine reference so go ahead and set this routine
			// as the *default* routine for all callbacks
			//
			SET_CALLBACK( CALLBACK_TIMER,          pSv );
			SET_CALLBACK( CALLBACK_START,          pSv );

			SET_CALLBACK( CALLBACK_STOP,           pSv );
			SET_CALLBACK( CALLBACK_PAUSE,          pSv );
			SET_CALLBACK( CALLBACK_CONTINUE,       pSv );
			SET_CALLBACK( CALLBACK_INTERROGATE,    pSv );
			SET_CALLBACK( CALLBACK_SHUTDOWN,       pSv );
			SET_CALLBACK( CALLBACK_PARAMCHANGE,    pSv );
			SET_CALLBACK( CALLBACK_NETBINDADD,     pSv );
			SET_CALLBACK( CALLBACK_NETBINDREMOVE,  pSv );
			SET_CALLBACK( CALLBACK_NETBINDENABLE,  pSv );
			SET_CALLBACK( CALLBACK_NETBINDDISABLE, pSv );
			SET_CALLBACK( CALLBACK_DEVICEEVENT,    pSv );
			SET_CALLBACK( CALLBACK_HARDWAREPROFILECHANGE,    pSv );
			SET_CALLBACK( CALLBACK_POWEREVENT,     pSv );
			SET_CALLBACK( CALLBACK_SESSIONCHANGE,  pSv );
#ifdef SERVICE_CONTROL_PRESHUTDOWN			
			SET_CALLBACK( CALLBACK_PRESHUTDOWN,    pSv );
#endif // SERVICE_CONTROL_PRESHUTDOWN			

			SET_CALLBACK( CALLBACK_USER_DEFINED,   pSv );
			
			//
			//	No longer supporting CALLBACK_RUNNING. It has been replaced by
			//	CALLBACK_TIMER. 
				SET_CALLBACK( CALLBACK_RUNNING,        pSv );
	        
		}
		else if( SVt_PVHV == SvTYPE( pSv ) )
		{
			HV* pHv = (HV*) pSv;
			//
			// We have a hash reference so set each callback routine to the appropriate
			// hash key's callback subroutine...
			//

			//
			//	CALLBACK_RUNNING has been superceeded by CALLBACK_TIMER.
			//	Scripts should only use "timer" instead of "running". However to support
			//	legacy scripts:
			//		1) If running is set then running will be supported
			//		2) If timer is set then the timer callback will occur
			//		3) if both are set then only "running" will occur (this mitigates the
			//		   problem of calling RegisterCallback() with only one callback routine.
			//
			//	*** #3 needs to be reconsidered: We should only use "timer" and encourage 
			//		authors to do the right thing.
			//
			SET_CALLBACK( CALLBACK_RUNNING,		   HASH_GET_SV( pHv, CALLBACK_NAME_RUNNING ) );
			
			SET_CALLBACK( CALLBACK_TIMER,          HASH_GET_SV( pHv, CALLBACK_NAME_TIMER ) );
			SET_CALLBACK( CALLBACK_START,          HASH_GET_SV( pHv, CALLBACK_NAME_START ) );
			SET_CALLBACK( CALLBACK_STOP,           HASH_GET_SV( pHv, CALLBACK_NAME_STOP ) );
			SET_CALLBACK( CALLBACK_PAUSE,          HASH_GET_SV( pHv, CALLBACK_NAME_PAUSE ) );
			SET_CALLBACK( CALLBACK_CONTINUE,       HASH_GET_SV( pHv, CALLBACK_NAME_CONTINUE ) );
			SET_CALLBACK( CALLBACK_INTERROGATE,    HASH_GET_SV( pHv, CALLBACK_NAME_INTERROGATE ) );
			SET_CALLBACK( CALLBACK_SHUTDOWN,       HASH_GET_SV( pHv, CALLBACK_NAME_SHUTDOWN ) );
			SET_CALLBACK( CALLBACK_PARAMCHANGE,    HASH_GET_SV( pHv, CALLBACK_NAME_PARAMCHANGE ) );
			SET_CALLBACK( CALLBACK_NETBINDADD,     HASH_GET_SV( pHv, CALLBACK_NAME_NETBINDADD ) );
			SET_CALLBACK( CALLBACK_NETBINDREMOVE,  HASH_GET_SV( pHv, CALLBACK_NAME_NETBINDREMOVE ) );
			SET_CALLBACK( CALLBACK_NETBINDENABLE,  HASH_GET_SV( pHv, CALLBACK_NAME_NETBINDENABLE ) );
			SET_CALLBACK( CALLBACK_NETBINDDISABLE, HASH_GET_SV( pHv, CALLBACK_NAME_NETBINDDISABLE ) );
			SET_CALLBACK( CALLBACK_DEVICEEVENT,    HASH_GET_SV( pHv, CALLBACK_NAME_DEVICEEVENT ) );
			SET_CALLBACK( CALLBACK_HARDWAREPROFILECHANGE,    HASH_GET_SV( pHv, CALLBACK_NAME_HARDWAREPROFILECHANGE ) );
			SET_CALLBACK( CALLBACK_POWEREVENT,     HASH_GET_SV( pHv, CALLBACK_NAME_POWEREVENT ) );
			SET_CALLBACK( CALLBACK_SESSIONCHANGE,  HASH_GET_SV( pHv, CALLBACK_NAME_SESSIONCHANGE ) );
#ifdef SERVICE_CONTROL_PRESHUTDOWN			
			SET_CALLBACK( CALLBACK_PRESHUTDOWN,    HASH_GET_SV( pHv, CALLBACK_NAME_PRESHUTDOWN ) );
#endif // SERVICE_CONTROL_PRESHUTDOWN

			SET_CALLBACK( CALLBACK_USER_DEFINED,   HASH_GET_SV( pHv, CALLBACK_NAME_USER_DEFINED ) );

			

		}
		else
		{
			// Fail
			RETVAL = FALSE;
		}
#endif // ENABLE_CALLBACKS
	}

	OUTPUT:
		RETVAL


UV
StartService( ... )

	PREINIT:
		UINT uintCallbackTimerValue = DEFAULT_CALLBACK_TIMER;

	CODE:
	{
		if( 2 < items )
		{
			croak( "Usage: " EXTENSION "::StartService( [ \\%Context [, $CallbackTimer] ] )\n" );
		}

		//    BeginServiceThread()
		ALERT( "Daemon::StartService: About to start the service thread..." );

		if( 0 == ghServiceThread )
		{
			//
			// Call a Win32 User level function to create a thread based message queue
			// This is needed since there are no other windows already created therefore
			// no message queue already exists.
			// NOTE that we are doing this BEFORE we create the service thread. This is so
			// that we can catch any message posted by the service thread even before we
			// are ready to handle them.
			//
			GetDesktopWindow();
			GetWindow( NULL, GW_HWNDFIRST );

			//
			//	Create a service thread which will result in the SCM creating yet another
			//	thread which ServiceMain() will run on.
			//
			ghServiceThread = CreateThread( NULL, 0, ServiceThread, 0, 0, &gServiceThreadId );

			ALERT( "Daemon::StartService: Thread has been created (falling back to perl)." );
#ifdef ENABLE_CALLBACKS
			//
			// If a context hash was passed in then store it for callback use
			//
			if( items )
			{
				if( 1 < items )
				{
					// Get the callback timer value...
                                        uintCallbackTimerValue = (int)SvIV( ST( 1 ) );
				}

				SV *pSv = ST( 0 );

				//
				// Do we have a reference?
				//
				if( SvROK( pSv ) )
				{
					// 
					// Yep. Let's dereference the reference....
					pSv = SvRV( pSv );
				}

				//
				// Now, do we have a hash? If so then store it.
				//
				if( SVt_PVHV == SvTYPE( pSv ) )
				{
					gpHvContext = (HV*) pSv;
				}
			}

			// If we have provided callbacks then enter a message loop calling the callbacks
			// whenever we receive a message to do so...
			if( 0 < gCallback.GetCount() )
			{
				//
				// Start the thread based message loop. Notice that we are relying on 
				// a *thread* message loop and now a window message loop. Any posting must
				// be made to the thread using PostThreadMessage() function.
				//
				MSG msg;
				BOOL fRet = FALSE;

				//
				// Store the main perl object into a global so that we can access it
				// when we need to callback...
				//
   				//	UPDATE: We no longer are using gpPerlObject. Instead reference the
				//	aTHX macro.
				//	gpPerlObject = aTHX;

	    		
				//
				// Set the global flag indicating that we are now in callback mode...
				//
				gfCallbackMode = TRUE;

				ALERT( "ENTERING the callback thread message loop." );
	            
				//
				//	Update the callback timer value.
				//
				gCallbackTimer = uintCallbackTimerValue;
				
				//
				//	If we have set the callback timer to a valid time
				//	then we need to start it. Don't worry about checking
				//	for it's current state. The CCallbackTimer class manages
				//	all of that.
				//
				gCallbackTimer.Start();
				

				while( FALSE != gfCallbackMode && ( 0 != ( fRet = GetMessage( &msg, NULL, 0, 0 ) ) ) )
				{ 
					if( -1 == fRet )
					{
						ALERT( "Callback Thread Message Loop: GetMessage() returned -1. Aborting..." );
						// handle the error and possibly exit
						break;
					}
					else
					{
						TranslateMessage( &msg ); 
						//
						// No DispatchMessage() here since we are only dealing with
						// thread messages
						//
						// DispatchMessage( &msg ); 
						DispatchThreadMessage( &msg );
					}
				}
				
				//
				//	We are leaving the callback routine so stop any of the 
				//	timer based callbacks.
				//
				gCallbackTimer.Stop(); 

			}
#endif // ENABLE_CALLBACKS
		}

		RETVAL = PTR2UV(ghServiceThread);
	}
	
	OUTPUT:
		RETVAL


int
CallbackTimer( ... )

	CODE:
	{
		if( 1 < items )
		{
			croak( "Usage: " EXTENSION "::CallbackTimer( [ $CallbackTimer ] )\n" );
		}

		if( items )
		{
			//
			//	Set the callback timer
			//
                        gCallbackTimer = (int)SvIV( ST( 0 ) );
			
			//
			//	If we were already stopped then we need to manually start
			//	the timer now that it has been reset with a new value.
			//	If the timer value has been set with 0 then it has already
			//	stopped and trying to start again will do nothing.
			//
			gCallbackTimer.Start();
		}

		//
		//	Return the callback timer value
		//
		RETVAL = (int) gCallbackTimer.GetTimerValue();
	}

	OUTPUT:
		RETVAL

int
StopService()	

	PREINIT:
		DWORD dwTermStatus;
		BOOL fResult = FALSE;
	
	CODE:
	{
		DWORD dwPostCount = 5;
		while( 0 < dwPostCount-- && PostThreadMessage( gServiceMainThreadID, WM_QUIT, 0, 0 ) )
		{
			//
			//	Oops. Do NOT use "sleep(xxx)" instead of Win32's "Sleep(xxx)". The
			//	case sensitive "S" will call the wrong sleep() function; where the
			//	time is not in milliseconds but in seconds!!!
			//
			Sleep( 100 );
		}

		if( FALSE != GetExitCodeThread( ghServiceThread, &dwTermStatus ) )
		{
			if( STILL_ACTIVE == dwTermStatus )
			{
				UpdateServiceStatus( SERVICE_STOP_PENDING );
				Sleep( 1000 );
				GetExitCodeThread( ghServiceThread, &dwTermStatus );
				if( STILL_ACTIVE == dwTermStatus )
				{
					TerminateThread( ghServiceThread, 0 );
				}
			}
			UpdateServiceStatus( SERVICE_STOPPED );
			fResult = TRUE;
		}

		//
		// If we were in callback mode then unset the mode...
		//
		gfCallbackMode = FALSE;

		RETVAL = (int) fResult;
	}

	OUTPUT:
		RETVAL


LPTSTR
GetVersion()

	CODE:
	{
		RETVAL = (LPTSTR) VERSION_STRING;
	}

	OUTPUT:
		RETVAL


int
CreateService( pSvServiceInfo )
	SV*	pSvServiceInfo

	PREINIT:
		HV	*pHv = NULL;
		BOOL fResult = FALSE;

	CODE:
	{
		//////////////////////////////////////////////////////////////////
		//  NOTE: dwServiceType does not include the SERVICE_INTERACTIVE_PROCESS 
		//        flag since this will prevent a assigning a userid other than
		//        LocalSystem.
		if( 1 != items )
		{
			croak( "Usage: " EXTENSION "::CreateService( \\%ServiceInfo )\n" );
		}

		if( NULL != ( pHv = EXTRACT_HV( pSvServiceInfo ) ) )
		{
			TCHAR szBuffer[ MAX_PATH * 2 ];
			TCHAR szBinaryPath[ MAX_PATH ];     
			TCHAR szDependencies[ MAX_SERVICE_DEPENDENCY_BUFFER_SIZE ]; 
			const char *pszDepend =      szDependencies;
			const char *pszMachine =     HASH_GET_PV( pHv, KEYWORD_SERVICE_MACHINE );
			const char *pszServiceName = HASH_GET_PV( pHv, KEYWORD_SERVICE_NAME );
			const char *pszDisplayName = HASH_GET_PV( pHv, KEYWORD_SERVICE_DISPLAY_NAME );
			const char *pszUser =        HASH_GET_PV( pHv, KEYWORD_SERVICE_ACCOUNT_UID );
			const char *pszPwd =         HASH_GET_PV( pHv, KEYWORD_SERVICE_ACCOUNT_PWD );
			const char *pszBinaryPath =  HASH_GET_PV( pHv, KEYWORD_SERVICE_BINARY_PATH );
			const char *pszParameters =  HASH_GET_PV( pHv, KEYWORD_SERVICE_PARAMETERS );
			const char *pszDescription = HASH_GET_PV( pHv, KEYWORD_SERVICE_DESCRIPTION );
			const char *pszLoadOrder = NULL;
			DWORD  dwTag = 0;
			DWORD dwAccess = SERVICE_ALL_ACCESS;
			DWORD dwServiceType = SERVICE_WIN32_OWN_PROCESS;
			DWORD dwStartType = SERVICE_AUTO_START;
			DWORD dwErrorControl = SERVICE_ERROR_IGNORE;

			ZeroMemory( szDependencies, sizeof( szDependencies ) );
			if( HASH_KEY_EXISTS( pHv, KEYWORD_SERVICE_DEPENDENCIES ) )
			{
				AV *pAv = NULL;
				if( NULL != ( pAv = HASH_GET_AV( pHv, KEYWORD_SERVICE_DEPENDENCIES ) ) )
				{
					LPTSTR pszBuffer = szDependencies;
					// av_len() returns -1 if no entries. Otherwise it returns the 
					// largest index number in the array.
					DWORD dwCount = av_len( pAv ) + 1;
					for( DWORD dwIndex = 0; dwIndex < dwCount; dwIndex++ )
					{
						_tcscpy( pszBuffer, ARRAY_GET_PV( pAv, dwIndex ) );
						pszBuffer = &szDependencies[ _tcslen( pszBuffer ) + 1 ];
					}
					// Add the final terminating null (string must be double null terminated)
					pszBuffer[0] = '\0';
				}
			}

			//  Only pad the pszBinaryPath with double quotes IF there are none already AND
			//  there are spaces in the path
			if( ( _tcschr( pszBinaryPath, ' ' ) && ( 0 != _tcsncmp( pszBinaryPath, TEXT( "\"" ), 1 ) ) ) )
			{
				_tcscpy( szBinaryPath, TEXT( "\"" ) );
				_tcscat( szBinaryPath, pszBinaryPath );
				_tcscat( szBinaryPath, TEXT( "\"" ) );
				pszBinaryPath = szBinaryPath;
			}

			//  Parameters are attached to the end of the binary path
			if( 0 != _tcscmp( TEXT( "" ), pszParameters ) )
			{
				_tcscpy( szBuffer, pszBinaryPath );
				_tcscat( szBuffer, TEXT( " " ) );
				_tcscat( szBuffer, pszParameters );
				pszBinaryPath = szBuffer;
			}

			if( ( 0 == _tcscmp( TEXT( "" ), pszUser ) )
				|| ( 0 == _tcsicmp( TEXT( "localsystem" ), pszUser ) ) )
			{
				pszUser = NULL;
				pszPwd = NULL;
			}

			if( HASH_KEY_EXISTS( pHv, KEYWORD_SERVICE_TYPE ) )
			{
                                dwServiceType = (DWORD)HASH_GET_IV( pHv, KEYWORD_SERVICE_TYPE );
			}

			if( HASH_KEY_EXISTS( pHv, KEYWORD_SERVICE_START_TYPE ) )
			{
                                dwStartType = (DWORD)HASH_GET_IV( pHv, KEYWORD_SERVICE_START_TYPE );
			}

			if( HASH_KEY_EXISTS( pHv, KEYWORD_SERVICE_ERROR_CONTROL ) )
			{
                                dwErrorControl = (DWORD)HASH_GET_IV( pHv, KEYWORD_SERVICE_ERROR_CONTROL );
			}

			OPEN_SERVICE_CONTROL_MANAGER( pszMachine )
				SC_HANDLE hService = CreateService( hSc,
													pszServiceName,
													pszDisplayName,
													dwAccess,
													dwServiceType,
													dwStartType,
													dwErrorControl,
													pszBinaryPath,
													pszLoadOrder,
													NULL,
													pszDepend,
													pszUser,
													pszPwd );
				if( NULL != hService )
				{
					fResult = TRUE;
					StoreServiceDescription( pszMachine, pszServiceName, pszDescription );
					CloseServiceHandle( hService );
				}
				else
				{
					gdwLastError = GetLastError();
				}
			CLOSE_SERVICE_CONTROL_MANAGER
		}

		RETVAL = ( 0 != fResult );
	}

	OUTPUT:
		RETVAL


int
DeleteService( ... )

	PREINIT:
		HV *pHv = NULL;
		BOOL fResult = FALSE;
		const char *pszServiceName = NULL;
		const char *pszMachine = TEXT( "" );
		DWORD dwIndex = 0;

	CODE:
	{
		if( ( 1 > items ) || ( 2 < items ) )
		{
			croak( "Usage: " EXTENSION "::DeleteService( [$Machine,] $ServiceName )\n" );
		}

		if( 2 == items )
		{
			pszMachine = SvPV_nolen( ST( dwIndex ) );
			dwIndex++;
		}

		if( NULL != ( pszServiceName = SvPV_nolen( ST( dwIndex ) ) ) )
		{
	        
			OPEN_SERVICE_CONTROL_MANAGER( pszMachine )
				SC_HANDLE hService = OpenService( hSc,
													pszServiceName,
													DELETE );
				if( NULL != hService )
				{
					if( FALSE == ( fResult = DeleteService( hService ) ) )
					{
						gdwLastError = GetLastError();    
					}
					CloseServiceHandle( hService );
				}
				else
				{
					gdwLastError = GetLastError();
				}
			CLOSE_SERVICE_CONTROL_MANAGER
		}
		RETVAL = ( 0 != fResult );
	}

	OUTPUT:
		RETVAL


int
ConfigureService( pSvServiceInfo )
	SV	*pSvServiceInfo

	PREINIT:
		HV *pHv = NULL;
		BOOL fResult = FALSE;

	CODE:
	{
		if( 1 != items )
		{
			croak( "Usage: " EXTENSION "::ConfigureService( \\%ServiceInfo )\n" );
		}

		if( NULL != ( pHv = EXTRACT_HV( pSvServiceInfo ) ) )
		{
			TCHAR szBuffer[ MAX_PATH * 2 ];
			TCHAR szBinaryPath[ MAX_PATH ];
			TCHAR szDependencies[ MAX_SERVICE_DEPENDENCY_BUFFER_SIZE ];
			const char *pszMachine = HASH_GET_PV( pHv, KEYWORD_SERVICE_MACHINE );
			const char *pszServiceName = NULL;
			const char *pszDisplayName = NULL;
			const char *pszUser =        NULL;
			const char *pszPwd =         NULL;
			const char *pszBinaryPath =  NULL;
			const char *pszParameters =  NULL;
			const char *pszLoadOrder =   NULL;
			const char *pszDepend =      NULL;
			const char *pszDescription = NULL;
			DWORD  dwTagId =        0;
			DWORD  *pdwTagId =      NULL;
			DWORD dwAccess =        SERVICE_ALL_ACCESS;
			DWORD dwServiceType =   SERVICE_NO_CHANGE;
			DWORD dwStartType =     SERVICE_NO_CHANGE;
			DWORD dwErrorControl =  SERVICE_NO_CHANGE;

			if( HASH_KEY_EXISTS( pHv, KEYWORD_SERVICE_NAME ) )
			{
				pszServiceName = HASH_GET_PV( pHv, KEYWORD_SERVICE_NAME );

			}

			if( HASH_KEY_EXISTS( pHv, KEYWORD_SERVICE_DISPLAY_NAME ) )
			{
				pszDisplayName = HASH_GET_PV( pHv, KEYWORD_SERVICE_DISPLAY_NAME );

			}

			if( HASH_KEY_EXISTS( pHv, KEYWORD_SERVICE_ACCOUNT_UID ) )
			{
				pszUser = HASH_GET_PV( pHv, KEYWORD_SERVICE_ACCOUNT_UID );

			}

			if( HASH_KEY_EXISTS( pHv, KEYWORD_SERVICE_ACCOUNT_PWD ) )
			{
				pszPwd = HASH_GET_PV( pHv, KEYWORD_SERVICE_ACCOUNT_PWD );

			}

			if( HASH_KEY_EXISTS( pHv, KEYWORD_SERVICE_DEPENDENCIES ) )
			{
				pszDepend = HASH_GET_PV( pHv, KEYWORD_SERVICE_DEPENDENCIES );
			}

			if( HASH_KEY_EXISTS( pHv, KEYWORD_SERVICE_DESCRIPTION ) )
			{
				pszDescription = HASH_GET_PV( pHv, KEYWORD_SERVICE_DESCRIPTION );
			}

			if( HASH_KEY_EXISTS( pHv, KEYWORD_SERVICE_BINARY_PATH ) )
			{
				pszBinaryPath = HASH_GET_PV( pHv, KEYWORD_SERVICE_BINARY_PATH );
				if( ( _tcschr( pszBinaryPath, ' ' ) && ( 0 != _tcsncmp( pszBinaryPath, TEXT( "\\" ), 1 ) ) ) )
				{
					_tcscpy( szBinaryPath, "\"" );
					_tcscat( szBinaryPath, pszBinaryPath );
					_tcscat( szBinaryPath, "\"" );
					pszBinaryPath = szBinaryPath;
				}
			}

			if( HASH_KEY_EXISTS( pHv, KEYWORD_SERVICE_PARAMETERS ) )
			{
				//  Add 2 for double quote marks...
				//  TCHAR szPathBuffer[ MAX_PATH + 2 ];

				// TODO:
				// We should query the real path if pszBinaryPath == NULL!!!
				if( NULL != pszBinaryPath )
/*
				// If NULL == pszBinaryPath then we should go out and get the binary path
				// and strip out any padding " chars
				{
					// Go and find the binary path...
					GetBinaryPath( pszMachine, pszServiceName, szPathBuffer, sizeof( szPathBuffer ) );
					pszBinaryPath = szPathBuffer;
				}
*/
				pszParameters = HASH_GET_PV( pHv, KEYWORD_SERVICE_PARAMETERS );

				//  Parameters are attached to the end of the binary path
				if( 0 != _tcscmp( TEXT( "" ), pszParameters ) )
				{
					_tcscpy( szBuffer, pszBinaryPath );
					_tcscat( szBuffer, TEXT( " " ) );
					_tcscat( szBuffer, pszParameters );
					pszBinaryPath = szBuffer;
				}
			}

			if( HASH_KEY_EXISTS( pHv, KEYWORD_SERVICE_DEPENDENCIES ) )
			{
				AV *pAv = NULL;
				if( NULL != ( pAv = HASH_GET_AV( pHv, KEYWORD_SERVICE_DEPENDENCIES ) ) )
				{
					LPTSTR pszBuffer = szDependencies;
					pszDepend = szDependencies;
					// av_len() returns -1 if no entries. Otherwise it returns the 
					// largest index number in the array.
					DWORD dwCount = av_len( pAv ) + 1;
					for( DWORD dwIndex = 0; dwIndex < dwCount; dwIndex++ )
					{
						_tcscpy( pszBuffer, ARRAY_GET_PV( pAv, dwIndex ) );
						pszBuffer = &szDependencies[ _tcslen( pszBuffer ) + 1 ];
					}
					// Add the final terminating null (string must be double null terminated)
					pszBuffer[0] = '\0';
				}
			}

			if( HASH_KEY_EXISTS( pHv, KEYWORD_SERVICE_TAG_ID ) )
			{
                                dwTagId = (DWORD)HASH_GET_IV( pHv, KEYWORD_SERVICE_TAG_ID );
				pdwTagId = &dwTagId;
			}

			if( HASH_KEY_EXISTS( pHv, KEYWORD_SERVICE_TYPE ) )
			{
                                dwServiceType = (DWORD)HASH_GET_IV( pHv, KEYWORD_SERVICE_TYPE );
			}

			if( HASH_KEY_EXISTS( pHv, KEYWORD_SERVICE_START_TYPE ) )
			{
                                dwStartType = (DWORD)HASH_GET_IV( pHv, KEYWORD_SERVICE_START_TYPE );
			}

			if( HASH_KEY_EXISTS( pHv, KEYWORD_SERVICE_ERROR_CONTROL ) )
			{
                                dwErrorControl = (DWORD)HASH_GET_IV( pHv, KEYWORD_SERVICE_ERROR_CONTROL );
			}

			OPEN_SERVICE_CONTROL_MANAGER( pszMachine )
				SC_HANDLE hService = OpenService( hSc, pszServiceName, SERVICE_CHANGE_CONFIG );
				if( NULL != hService )
				{
					fResult = ChangeServiceConfig(  hService,
													dwServiceType,
													dwStartType,
													dwErrorControl,
													pszBinaryPath,
													pszLoadOrder,
													pdwTagId,
													pszDepend,
													pszUser,
													pszPwd,
													pszDisplayName );
					if( ( TRUE == fResult ) && ( NULL != pszDescription ) )
					{
						StoreServiceDescription( pszMachine, pszServiceName, pszDescription );
					}
	                
					if( FALSE == fResult )
					{
						gdwLastError = GetLastError();
					}
					CloseServiceHandle( hService );
				}
				else
				{
					gdwLastError = GetLastError();
				}
			CLOSE_SERVICE_CONTROL_MANAGER
		}
		RETVAL = ( 0 != fResult );
	}

	OUTPUT:
		RETVAL


int
QueryServiceConfig( pSvServiceInfo )
	SV *pSvServiceInfo

	PREINIT:
		HV *pHv = NULL;
		BOOL fResult = FALSE;

	CODE:
	{
		//////////////////////////////////////////////////////////////////
		//  Note:  This returns the service configuration for the *next*
		//         time that the service will run. lIf you modify the
		//         configuration while the service is still running then
		//         a call to this function will not return the current
		//         configuration but the configuration that will be
		//         used next time the service is run.    
		if( 1 != items )
		{
			croak( "Usage: " EXTENSION "::QueryServiceConfig( \\%ServiceInfo )\n" );
		}

		if( NULL != ( pHv = EXTRACT_HV( pSvServiceInfo) ) )
		{
			TCHAR szServiceName[ 256 ];
			TCHAR szMachineName[ 256 ];
			const char *pszServiceName = HASH_GET_PV( pHv, KEYWORD_SERVICE_NAME );
			const char *pszMachineName = HASH_GET_PV( pHv, KEYWORD_SERVICE_MACHINE );

			// Copy the service name to a local buffer so we can clear out the
			// hash...
			// The HASH_GET_PV() macro never returns NULL, instead it returns ""
			_tcscpy( szServiceName, pszServiceName );
			pszServiceName = szServiceName;

			// Copy the machine name to a local buffer so we can clear out the
			// hash...
			// ...if no machine key was specified HASH_GET_PV() will return "" 
			// (not a NULL). Later we will call OpenSCManager() passing in this value
			// which is interpretted as the local machine.
			_tcscpy( szMachineName, pszMachineName );
			pszMachineName = szMachineName;

			hv_clear( pHv );

			OPEN_SERVICE_CONTROL_MANAGER_READ( pszMachineName )
				SC_HANDLE hService = OpenService( hSc, pszServiceName, SERVICE_QUERY_CONFIG );
				if( NULL != hService )
				{
					DWORD dwBufferSize = 0;
					fResult = QueryServiceConfig( hService, NULL, 0, &dwBufferSize );
					if( ERROR_INSUFFICIENT_BUFFER == GetLastError() )
					{
						LPQUERY_SERVICE_CONFIG pServiceConfig = NULL;
						pServiceConfig = (LPQUERY_SERVICE_CONFIG) new BYTE [ dwBufferSize ];
						if( NULL != pServiceConfig )
						{
							ZeroMemory( pServiceConfig, dwBufferSize );
							fResult = QueryServiceConfig( hService, pServiceConfig, dwBufferSize, &dwBufferSize );
							if( FALSE != fResult )
							{
								AV *pAv = newAV();

								HASH_STORE_IV( pHv, KEYWORD_SERVICE_TYPE, pServiceConfig->dwServiceType );
								HASH_STORE_IV( pHv, KEYWORD_SERVICE_START_TYPE, pServiceConfig->dwStartType );
								HASH_STORE_IV( pHv, KEYWORD_SERVICE_ERROR_CONTROL, pServiceConfig->dwErrorControl );
								HASH_STORE_PV( pHv, KEYWORD_SERVICE_BINARY_PATH, pServiceConfig->lpBinaryPathName );
								HASH_STORE_PV( pHv, KEYWORD_SERVICE_LOAD_ORDER, pServiceConfig->lpLoadOrderGroup );
								HASH_STORE_PV( pHv, KEYWORD_SERVICE_DISPLAY_NAME, pServiceConfig->lpDisplayName );
								HASH_STORE_PV( pHv, KEYWORD_SERVICE_ACCOUNT_UID, pServiceConfig->lpServiceStartName );
								HASH_STORE_PV( pHv, KEYWORD_SERVICE_NAME, pszServiceName );
								HASH_STORE_PV( pHv, KEYWORD_SERVICE_MACHINE, pszMachineName );

								if( NULL != pAv )
								{
									LPTSTR pszBuffer = pServiceConfig->lpDependencies;

									while( '\0' != pszBuffer[0] )
									{
										ARRAY_PUSH_PV( pAv, pszBuffer );
										pszBuffer = &pszBuffer[ _tcslen( pszBuffer ) + 1 ];
									}
									HASH_STORE_AV( pHv, KEYWORD_SERVICE_DEPENDENCIES, pAv );
								}

								// Get the service Description...
								{
									HKEY hRoot = HKEY_LOCAL_MACHINE;
									HKEY hKey = NULL;
									BOOL fFlag = TRUE;
	            
									if( 0 != _tcscmp( TEXT( "" ), pszMachineName ) )
									{
										fFlag = ( ERROR_SUCCESS == RegConnectRegistry( pszMachineName, HKEY_LOCAL_MACHINE, &hRoot ) );
									}

									if( TRUE == fFlag )
									{
										TCHAR szBuffer[ 75 ];
										_stprintf( szBuffer, REGISTRY_SERVICE_PATH TEXT( "\\%s" ), pszServiceName );
										if( ERROR_SUCCESS == RegOpenKeyEx( hRoot, szBuffer, 0, KEY_QUERY_VALUE, &hKey ) )
										{
											TCHAR szDescription[ 1024 ];
											DWORD dwType, dwSize;
											if( ( ERROR_SUCCESS == RegQueryValueEx( hKey, REGISTRY_SERVICE_KEYWORD_DESCRIPTION, 0, &dwType, (LPBYTE) szDescription, &dwSize )
												&& ( REG_SZ == dwType ) ) )
											{
												HASH_STORE_PV( pHv, KEYWORD_SERVICE_DESCRIPTION, szDescription );                    
											}
										}
										RegCloseKey( hKey );
									}
	                
									if( 0 != _tcscmp( TEXT( "" ), pszMachineName ) )
									{
										RegCloseKey( hRoot );
									}
	                    
								}

							}
							else
							{
								gdwLastError = GetLastError();
							}
							delete [] (BYTE*) pServiceConfig;
						}
					}
					else
					{
						gdwLastError = GetLastError();
					}
					CloseServiceHandle( hService );
				}
				else
				{
					gdwLastError = GetLastError();
				}
			CLOSE_SERVICE_CONTROL_MANAGER
		}
		RETVAL = ( 0 != fResult );
	}

	OUTPUT:
		RETVAL


int
SetServiceBits( dwBits = 0, ... )
	DWORD dwBits	

	PREINIT:
		BOOL fResult = FALSE;
		SERVICE_STATUS_HANDLE hService = ghService;

	CODE:
	{
		if( ( 1 > items ) || ( 2 < items ) )
		{
			croak( "Usage: SetServiceBits( $Value, [$hServiceHandle] )\n" );
		}

		if( 2 == items )
		{
			hService = (SERVICE_STATUS_HANDLE) SvIV( ST( 1 ) );
		}

		if( 0 != hService )
		{

			// SetServiceBits() for some reason will not link. The link lib
			// is AdvApi32.dll which is in the link list but alas it errors out.
			// So we need to fix this later. For now we hack...load the dll, get the
			// proc then call it.

			//  Now set our bits...
			fResult = My_SetServiceBits( (SERVICE_STATUS_HANDLE) ghService, (DWORD) dwBits,(BOOL) TRUE, (BOOL) TRUE );
		}
		else
		{
			// Set the gdwServiceBits so that when the service is formally running
			// it will set the bits.
			gdwServiceBits = dwBits;
			fResult = 1;
		}
		RETVAL = (int) fResult;
	}

	OUTPUT:
		RETVAL


DWORD
GetLastError()

	CODE:
	{
		RETVAL = gdwLastError;
	}

	OUTPUT:
		RETVAL



DWORD
State( ... )

	PREINIT:
	
	CODE:
	{
		if( 2 < items )
		{
			croak( "Usage: State( [$State [, $WaitHint ] || \\%Hash ] )\n" );
		}

		if( 0 != gdwState )
		{

			if( items )
			{
				SV *pSv = ST( 0 );
				HV *pHv = NULL;
				DWORD dwState = gdwState;
				DWORD dwWaitHint = DEFAULT_WAIT_HINT;
				DWORD dwError = NO_ERROR;

				if( NULL != ( pHv = EXTRACT_HV( pSv ) ) )
				{
					if( HASH_KEY_EXISTS( pHv, KEYWORD_STATE_STATE ) )
					{
                                                dwState     = (DWORD)HASH_GET_IV( pHv, KEYWORD_STATE_STATE );
					}
					if( HASH_KEY_EXISTS( pHv, KEYWORD_STATE_WAIT_HINT ) )
					{
                                                dwWaitHint  = (DWORD)HASH_GET_IV( pHv, KEYWORD_STATE_WAIT_HINT );
					}
					if( HASH_KEY_EXISTS( pHv, KEYWORD_STATE_ERROR ) )
					{
                                                dwError     = (DWORD)HASH_GET_IV( pHv, KEYWORD_STATE_ERROR );
					}
				}
				else
				{
                                        dwState = (DWORD)SvIV( pSv );
					if( 2 == items )
					{
						// Assume that the hint was in milliseconds
                                                dwWaitHint = (DWORD)SvIV( ST( 1 ) );
					}
				}
				
				UpdateServiceStatus( dwState, dwWaitHint, dwError );
			}
		}
		RETVAL = gdwState;
	}
	
	OUTPUT:
		RETVAL



DWORD
QueryLastMessage( ... )

	CODE:
	{
		if( 1 < items )
		{
			croak( "Usage: QueryLastMessage( [$fResetMessage] )\n" );
		}

		if( ( 1 == items ) && ( 0 != SvIV( ST( 0 ) ) ) )
		{
			gdwLastControlMessage = SERVICE_CONTROL_NONE;
		}

		RETVAL = gdwLastControlMessage;
	}

	OUTPUT:
		RETVAL



BOOL
ShowService( pszWindowStation = TEXT( "Winsta0" ), ... )
	char *pszWindowStation

	PREINIT:
		BOOL fResult;
		const char *pszDesktop = TEXT( "Default" );

	CODE:
	{
		if( 3 < items )
		{
			croak( "Usage: ShowService( $WindowStationName, [$DesktopName] )\n" );
		}

		if( 1 < items )
		{
			pszDesktop = (LPTSTR) SvPV_nolen( ST( 0 ) );
		}

		// Free your mind...and the current console. :)
		// Do it before the winstation/desktop switch otherwise if you free it later you will
		// see a brief flash of a console.
		FreeConsole();

		ALERT( "ShowService: About to call gWindowStation.Set()\n" );
		fResult = gWindowStation.Set( pszWindowStation, pszDesktop );
#ifdef _DEBUG
		TCHAR szBuffer[256];
		wsprintf( szBuffer,
				  TEXT( "ShowService: Setting window station %s\\%s resulted in %d" ),
				  pszWindowStation,
				  pszDesktop,
				  fResult );
		ALERT( szBuffer );
#endif

		// Allocate a new console for output.
		AllocConsole();

		RETVAL = (BOOL) fResult;
	}

	OUTPUT:
		RETVAL


BOOL
HideService()

	CODE:
	{
		RETVAL = (BOOL) gWindowStation.Set( TEXT( "Service-x0-3e7$" ), TEXT( "Default" ) );
	}

	OUTPUT:
		RETVAL


BOOL
RestoreService()

	CODE:	
	{
		RETVAL = (BOOL) gWindowStation.Restore();
	}

	OUTPUT:
		RETVAL



DWORD
Timeout( ... )

	CODE:
	{
		if( 1 < items )
		{
			croak( TEXT( "Usage: " EXTENSION "::Timeout( [$Timeout] )\n" ) );
		}

		if( items )
		{
                        gdwHandlerTimeout = (DWORD)SvIV( ST( 0 ) );
		}
		RETVAL = gdwHandlerTimeout;
	}

	OUTPUT:
		RETVAL



UV
GetServiceHandle()

	CODE:	
	{
		if( items )
		{
			croak( TEXT( "Usage: " EXTENSION "::GetServiceHandle()\n" ) );
		}

		RETVAL = PTR2UV(ghService);
	}

	OUTPUT:
		RETVAL



DWORD
AcceptedControls( ... )

	CODE:	
	{
		if( 1 < items )
		{
			croak( TEXT( "Usage: " EXTENSION "::AcceptedControls( [$NewControls] )\n" ) );
		}

		if( 0 < items )
		{
                        gdwControlsAccepted = (DWORD)SvIV( ST( 0 ) );
		}
		RETVAL = gdwControlsAccepted;
	}

	OUTPUT:
		RETVAL


BOOL
SetSecurity( pszMachine, pszServiceName, pSvSecurityObject )
	LPTSTR pszMachine
	LPTSTR pszServiceName
	SV	*pSvSecurityObject

	PREINIT:
		SECURITY_DESCRIPTOR *pSD = NULL;
		BOOL fResult = FALSE;

	CODE:
	{
		if( 3 < items )
		{
			croak( TEXT( "Usage: " EXTENSION "::AcceptedControls( $Machine, $ServiceName, $Win32::Perms_Object | $BinarySD )\n" ) );
		}

		if( sv_isobject( (SV*) pSvSecurityObject ) )
		{
			// Is pSD an object (blessed object)?
			LPTSTR pszObjectType = NULL;
			SV *pSvTemp = NULL;

			//  Yep, it's a reference to a blessed object...
			//  This means that pSv is actually a blessed HV*
			pSvTemp = SvRV( (SV*) pSvSecurityObject );
			pszObjectType = HvNAME( SvSTASH( pSvTemp ) );

			if( 0 == _stricmp( pszObjectType, PERL_WIN32_PERMS_EXTENSION  ) )
			{
				dSP;
				int iCount;

				//  We have a Win32::Perms object. So let's call into its GetSD() method
				//  to get a pointer to the absolute Security Descriptor...
	        
				// Save our current position on the stack
				PUSHMARK( SP );
	        
				// Push the Win32::Perms object onto the stack
				XPUSHs( (SV*) pSvSecurityObject );

				//  Go back to the previously stored stack position
				PUTBACK;
	            
				//  Remember the position on the stack so when we free temp vars only up to this point
				//  will be freed
				ENTER;
				SAVETMPS;
	        
				//  Call the method...
				iCount = perl_call_method( (char*) "GetSD", G_SCALAR );

				if( 0 < iCount )
				{
					//  Yahooo! The method returned a value; assume it is a Security Descriptor pointer
					//  so pop it off as a long
					pSD = (SECURITY_DESCRIPTOR*) POPl;
				}

				//  Free all scalars created since the ENTER/SAVETMPS combo
				FREETMPS;
				LEAVE;
			}
		}
		else
		{
			// Okay, first check if we have an absolute security descriptor...
			pSD = (SECURITY_DESCRIPTOR*) SvIV( pSvSecurityObject );
			if( ! IsValidSecurityDescriptor( pSD ) )
			{
				// Hmmm. Okay, let's try a relative security descriptor...
				pSD = (SECURITY_DESCRIPTOR*) SvPV_nolen( pSvSecurityObject );
				if( ! IsValidSecurityDescriptor( pSD ) )
				{
					// Sigh. No, we don't seem to have any security descriptor
					pSD = NULL;
				}
			}
		}
	    
		// If we have a valid Security Descriptor that use it!
		if( NULL != pSD )
		{
			BOOL fACLExists = FALSE;
			BOOL fTempBool = FALSE;
			PACL pTempACL = NULL;
			PSID pSid = NULL;
			DWORD dwFlags = 0;
			DWORD dwOpenFlags = READ_CONTROL | WRITE_DAC;

			if( NULL != pszServiceName )
			{
	        
				if( FALSE != GetSecurityDescriptorSacl( pSD, &fACLExists, &pTempACL, &fTempBool ) )
				{
					dwFlags |= SACL_SECURITY_INFORMATION;
					SetPrivilege( 0, SE_SECURITY_NAME, TRUE);
					dwOpenFlags |= ACCESS_SYSTEM_SECURITY;
				}

				fTempBool = fACLExists = FALSE;
				if( FALSE != GetSecurityDescriptorDacl( pSD, &fACLExists, &pTempACL, &fTempBool ) )
				{
					dwFlags |= DACL_SECURITY_INFORMATION;
					dwOpenFlags |= WRITE_DAC;
				}

	/*  
		//
		//  Don't try to set the owner...this only causes problems such as
		//  permission issues preventing the set from happening...
		//
				fTempBool = fACLExists = FALSE;
				if( FALSE != GetSecurityDescriptorOwner( pSD, &pSid, &fTempBool ) )
				{
					dwFlags |= OWNER_SECURITY_INFORMATION;
					dwOpenFlags |= WRITE_OWNER;
				}
	*/

				OPEN_SERVICE_CONTROL_MANAGER( pszMachine )
				SC_HANDLE hService = OpenService( hSc,
													pszServiceName,
													dwOpenFlags );
				if( NULL != hService )
				{

					// From MSDN...
					// Sets the object's system access control list (SACL). The hService handle must 
					// have ACCESS_SYSTEM_SECURITY access. The proper way to obtain this access is to 
					// enable the SE_SECURITY_NAME privilege in the caller's current access token, open 
					// the handle for ACCESS_SYSTEM_SECURITY access, and then disable the privilege.
					//
					// ...it does not mention if the owner of the service (the process itself) must
					// still do all this. But then again, there is no way to pass in such flags
					// when opening the SCM since we only register for the SCM callback. How odd.
					//

					if( FALSE != SetServiceObjectSecurity( hService, dwFlags, pSD ) )
					{
						fResult = TRUE;
					}
					else
					{
						gdwLastError = GetLastError();
					}

					if( SACL_SECURITY_INFORMATION & dwFlags )
					{
						// Try to disable the privilege since it is no longer needed.
						SetPrivilege( 0, SE_SECURITY_NAME, FALSE );
					}

					CloseServiceHandle( hService );
				}
				else
				{
					gdwLastError = GetLastError();
				}
				CLOSE_SERVICE_CONTROL_MANAGER
			}
		}

		RETVAL = fResult;
	}

	OUTPUT:
		RETVAL


void
GetSecurity( pszMachine, pszServiceName )
    LPTSTR pszMachine
	LPTSTR pszServiceName

	PREINIT:
		SV *pSv = NULL;
		SECURITY_DESCRIPTOR *pSD = NULL;
		BOOL fResult = FALSE;

	CODE:
	{
		//////////////////////////////////////////////////////////////////
		//
		//
		// TEST: p Win32::Daemon::GetSecurity( '', 'TlntSvr' )
		//
		if( 2 != items )
		{
			croak( TEXT( "Usage: " EXTENSION "::GetSecurity( $Machine, $ServiceName )\n" ) );
		}

		if( NULL != pszServiceName )
		{
	        
			// TODO:
			// We should remove this privilege at the end of this function...
			SetPrivilege( 0, SE_SECURITY_NAME, TRUE);

			OPEN_SERVICE_CONTROL_MANAGER( pszMachine )
			SC_HANDLE hService = OpenService( hSc,
												pszServiceName,
												ACCESS_SYSTEM_SECURITY | READ_CONTROL );
			if( NULL != hService )
			{
				DWORD dwBufferSize = 0;
				DWORD dwBytesNeeded = 1024;
				BOOL fContinue = TRUE;
	            
				SetPrivilege( 0, SE_SECURITY_NAME, TRUE);
	            
				do
				{
					fContinue = FALSE;
					if( NULL != pSD )
					{
						delete [] (BYTE*) pSD;
						pSD = NULL;
					}
					pSD = (SECURITY_DESCRIPTOR *) new BYTE[ dwBytesNeeded ];
					if( NULL != pSD )
					{
						dwBufferSize = dwBytesNeeded;
						ZeroMemory( pSD, dwBufferSize );
						if( FALSE == QueryServiceObjectSecurity( 
												hService, 
												DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, 
												pSD, 
												dwBufferSize, 
												&dwBytesNeeded ) )
						{
							DWORD dwError = GetLastError();
							if( ERROR_INSUFFICIENT_BUFFER == dwError || 0x000001e7 == dwError )
							{
								fContinue = TRUE;
							}
						}
					}
				} while( TRUE == fContinue );

				if( NULL != pSD )
				{
					pSv = newSVpv( (LPTSTR) pSD, dwBytesNeeded );
					delete [] (BYTE*) pSD;
					pSD = NULL;
				}

				CloseServiceHandle( hService );
			}
			else
			{
				gdwLastError = GetLastError();
			}
			CLOSE_SERVICE_CONTROL_MANAGER
		}

		if( NULL != pSv )
		{
			// Return the scalar...
			//		Original code was: PUSH_NOREF( pSv );
			//		...assuming that no need to make pSv mortal?
			ST(0) = pSv;
		}
		else
		{
			// Return undef
			ST(0) = &PL_sv_undef;
		}
	}

	OUTPUT:



char *
DebugOutputPath( ... )

	CODE:
	{		
		if( 1 < items )
		{
			croak( TEXT( "Usage: " EXTENSION "::DebugOutputPath( [$Path] )\n" ) );
		}
#ifdef _DEBUG
		if( items )
		{
			LPCTSTR pszDebugOutputPath = SvPV_nolen( ST( 0 ) );
			if( NULL != pszDebugOutputPath )
			{
				CreateLog( pszDebugOutputPath );
			}
		}
		
		RETVAL = (LPTSTR) gszDebugOutputPath;
#else	/* ! def _DEBUG */
		RETVAL = TEXT( "" );
#endif	/*	_DEBUG	*/
	}
	OUTPUT:
		RETVAL




DWORD
IsDebugBuild()
	
	CODE:
#ifdef _DEBUG
		RETVAL =  1;
#else
		RETVAL = 0;
#endif
 
	OUTPUT:
		RETVAL	


#	/*
#	HISTORY:
#
#		-20000618
#			-Added:
#				-ConfigureService
#				-QueryServiceConfig
#
#		-20011230 rothd@roth.net
#			- Fixed bug where service doesn't work properly with Windows NT 4. We were 
#			  defaulting by acccepting the SERVICE_ACCEPT_PARAMCHANGE and 
#			  SERVICE_ACCEPT_NETBINDCHANGE controls. However, they were introduced in 
#			  Win2k so NT 4 coughed up blood with them.
#
#		-20010224
#			-Added:
#				-RegisterCallbacks() (and callback support)
#
#		-20011205
#			-Added:
#				-AcceptedControls()
#
#		- 20020605 rothd@roth.net
#			- Added support for reporting service errors. You can now pass in a
#			  hash reference into State(). More details in the POD docs.
#
#		- 20030615 rothd@roth.net
#			- Added callback support (actually finished it!).
#			- Added security support (Set and Get SD).
#				-GetSecurity()
#				-SetSecurity()
#				-IsDebugBuild()
#				-CallbackTimer()			
#		
#		- 20061222 rothd@roth.net
#			- Converted to XS file.
#			- Fixed callback heartbeat: now properly calls back with SERVICE_RUNNING (not SERVICE_CONTROL_RUNNING)
#			- StopService() will post WM_QUIT message to the ServiceMain() thread to shut down the service thread.
#			- Calling into StopService() will auto change the state to STOPPING/STOPPED so you do not need to 
#				explicitly do so (calling State() or a callback returning STOPPING/STOPPED).
#			- Fixed bug where messages were posted to wrong thread.
#
#	*/