Topic 4.3

Windows Services

A Look into Windows Services

About Services

When updating an installation, it's good practice for you to stop the installed application and anything related with it before overwriting any of its files. The same goes for making PAFs. Since essentially we're installing our portable everytime we launch it, we need to be sure that a locally run service (if any) is stopped before executing our portable version. Then, of course, return the host's PC with it's original service and make sure it's running on exit.

The support for services is by default disabled in the official builds of PA.c Launcher. I'm sure they have their reasons for not allowing support for services just yet. I think there is more to it than just this but in the source code of PA.c Launcher it's commented with:

Currently services are disabled as:
  • they're not used yet (possibly unstable) and
  • the plug-in is fairly large (at time of reporting, 122591B vs. 96901B, 25KB larger)
TODO: switch back to NSIS code... got to sort out the null byte issue with dependencies.
With the PA.c Launcher that I'm currently developing I have eliminated the need to use the custom.nsh file to work with services. You can visit the GitHub project page here which will take you to the features section of the read me file for more information.
Finding Services

If you used Total Uninstall to capture an installation of a program than you will be able to tell if any services were installed as well. By looking at the image to the right you can see there's two services that were installed while capturing the installation of Virtual CloneDrive; ElbyCDIO and VClone. Keep reading, we are not out of the woods yet people.

Take note to the service name(s) that are shown to you like in the image above because that will be the name(s) you'll need to use for this next part. Go ahead and open up a command prompt; we're going to be using the sc command to find the service(s) configuration information. In the prompt screen go ahead and enter in sc qc ElbyCDIO (you would repeat for the second service VClone). You should get something resembling the following:

Microsoft Windows [Version 6.3.9600]
(c) 2013 Microsoft Corporation. All rights reserved.

C:\Windows\system32>sc qc ElbyCDIO
[SC] QueryServiceConfig SUCCESS

SERVICE_NAME: ElbyCDIO
        TYPE               : 1  KERNEL_DRIVER
        START_TYPE         : 1   SYSTEM_START
        ERROR_CONTROL      : 1   NORMAL
        BINARY_PATH_NAME   : System32\Drivers\ElbyCDIO.sys
        LOAD_ORDER_GROUP   :
        TAG                : 0
        DISPLAY_NAME       : ElbyCDIO Driver
        DEPENDENCIES       :
        SERVICE_START_NAME :

C:\Windows\system32>

So now you have all the information you need to deal with a service using the macros below. Line 8 tells you the type of service which in this case is a kernel driver. Line 9 tells you the start type which in this case is system start. Line 11 tells you where the services' file path is located on the system. Line 15 would also tell you if there were any dependencies but in this case there isn't any.

You would use the information with my build of PAL like this:
  • (Line 8) kernal
  • (Line 9) system
  • (Line 11) $SYSDIR\drivers\ElbyCDIO.sys—Copy this file to the PAF's folder somewhere (i.e. ..\App\DefaultData\drivers).
  • (Line 15) empty—Since this is empty you wouldn't use this below

If you're using my build of PAL to PAF your portable then you should have inside your Launcher.ini file something similiar to the code block below. You would repeat as needed for any other services, in this case [Service2]Name=VClone and so on.

[Service1]
Name=ElbyCDIO
Path=%PAL:DataDir%\drivers\ElbyCDIO.sys
Type=kernel
Start=system
IfExists=replace
; IfExists can also be skip
Macros

If you're PAF requires you to use a Windows service then you're in luck. I wrote some custom macros in which you can use in a PAF within the custom.nsh file. With the following code snippets, you can check for and stop a local service, create and start a portable service and return the host machine to it's original condition in ${SegmentUnload}.

In the following snippets you'll see the /DISABLEFSR parameter; this should only be used on x64 machines. However, if you mess up there's a failsafe in the macros that will dodge this bullet for you.

Service::Query

Discription
This macro will check the existance of a service. If the specified service is found it will then write in the PortableApps.comLauncherRuntimeData.ini file that the specified service is there and is true. If the specified service is not found then an error flag is set to which you can check for.
;=#
; ${Service::Query} "NAME" /DISABLEFSR $0 $1
;
;    ::Query     = Queries a service to see if it exists. 
;    NAME        = The Service name
;    /DISABLEFSR = Disables redirection if x64. Use "" to skip.
;    $0          = Return after call | 1060 = Service not found.
;    $1          =   ''    ''    ''
;
!define Service::Query `!insertmacro _Service::Query`
!macro _Service::Query _SVC _FSR _ERR1 _ERR2
	StrCmpS $Bit 64 0 +4
	StrCmp "${_FSR}" /DISABLEFSR 0 +3
	ExecDos::Exec /TOSTACK /DISABLEFSR `"${SC}" query "${_SVC}"`
	Goto +2
	ExecDos::Exec /TOSTACK `"${SC}" query "${_SVC}"`
	Pop ${_ERR1}
	Pop ${_ERR2}
	StrCmp ${_ERR1} "1060" 0 +3
	SetErrors
	Goto +2
	${WriteRuntimeData} "${_SVC}Service" LocalService true
!macroend

Service::QueryConfig

Discription
This macro will help retrieve the local services' binary path. First it'll try to locate it using the command line and if that doesn't work it will try to read the path from the registry. Once it's got the binary's path it will then write the value of it's location in the PortableApps.comLauncherRuntimeData.ini file for later use.
;=# 
; ${Service::QueryConfig} "NAME" /DISABLEFSR $0 $1
;
;    ::QueryConfig = The service's binary path is returned. 
;    NAME          = The Service name
;    /DISABLEFSR   = Disables redirection if x64. Use "" to skip.
;    $0            = Return after call | 1 = success
;    $1            =   ''    ''    ''  | Should be the file path
;
; $1 will now hold the path to it's binary executable or an error
;
!define Service::QueryConfig `!insertmacro _Service::QueryConfig`
!macro _Service::QueryConfig _SVC _FSR _ERR1 _ERR2
	ReadEnvStr $R0 COMSPEC
	StrCmpS $Bit 64 0 +4
	StrCmp "${_FSR}" /DISABLEFSR 0 +3
	ExecDos::Exec /TOSTACK /DISABLEFSR `"$R0" /c "${SC} qc "${_SVC}" | FIND "BINARY_PATH_NAME""`
	Goto +2
	ExecDos::Exec /TOSTACK `"$R0" /c "${SC} qc "${_SVC}" | FIND "BINARY_PATH_NAME""`
	Pop ${_ERR1}
	Pop ${_ERR2}
	StrCmpS ${_ERR1} 0 0 +4
	StrCpy $R1 ${_ERR2} "" 29
	${WriteRuntimeData} "${_SVC}Service" LocalPath "$R1"
	ReadRegStr $R1 HKLM `SYSTEM\CurrentControlSet\services\${_SVC}` ImagePath
	${WriteRuntimeData} "${_SVC}Service" LocalPath "$R1"
!macroend

Service::State

Discription
This macro will check to see if a local service is running and if so it will then write a value to show that it was running in the PortableApps.comLauncherRuntimeData.ini file for later use.
;=#
; ${Service::State} "NAME" /DISABLEFSR $0 $1
;
;    ::State     = The service's status is returned. 
;    NAME        = The Service name
;    /DISABLEFSR = Disables redirection if x64. Use "" to skip.
;    $0          = Return after call | 1 = success
;    $1          =   ''    ''    ''  | 1 = running
;
; $1 will now hold "1" if running or "0" if not
;
!define Service::State `!insertmacro _Service::State`
!macro _Service::State _SVC _FSR _ERR1 _ERR2
    ReadEnvStr $R0 COMSPEC
    StrCmpS $Bit 64 0 +4
    StrCmp "${_FSR}" /DISABLEFSR 0 +3
    ExecDos::Exec /TOSTACK /DISABLEFSR `"$R0" /c "${SC} query "${_SVC}" | find /C "RUNNING""`
    Goto +2
    ExecDos::Exec /TOSTACK `"$R0" /c "${SC} query "${_SVC}" | find /C "RUNNING""`
    Pop ${_ERR1}
    Pop ${_ERR2}
	StrCmpS ${_ERR1} 1 0 +4
	StrCmpS ${_ERR2} 1 0 +3
	${WriteRuntimeData} "${_SVC}Service" LocalState Running
!macroend

Service::Start

Discription
This macro will simply start a specified service. Can be used for both local and portable versions of services.
;=#
; ${Service::Start} "NAME" /DISABLEFSR $0 $1
;
;    ::Start     = Start a service. 
;    NAME        = The Service name
;    /DISABLEFSR = Disables redirection if x64. Use "" to skip.
;    $0          = Return after call
;    $1          =   ''    ''    ''
;
; $1 will now hold "1" if running or "0" if not
;
!define Service::Start `!insertmacro _Service::Start`
!macro _Service::Start _SVC _FSR _ERR1 _ERR2
    StrCmpS $Bit 64 0 +4
    StrCmp "${_FSR}" /DISABLEFSR 0 +3
    ExecDos::Exec /TOSTACK /DISABLEFSR `"${SC}" start "${_SVC}"`
    Goto +2
    ExecDos::Exec /TOSTACK `"${SC}" start "${_SVC}"`
    Pop ${_ERR1}
    Pop ${_ERR2}
!macroend

Service::Stop

Discription
This macro will simply stop a specified service. Can be used for both local and portable versions of services.
;=#
; ${Service::Stop} "NAME" /DISABLEFSR $0 $1
;
;    ::Stop      = Sends a STOP control request to a service. 
;    NAME        = The Service name
;    /DISABLEFSR = Disables redirection if x64. Use "" to skip.
;    $0          = Return after call
;    $1          =   ''    ''    ''
;
!define Service::Stop `!insertmacro _Service::Stop`
!macro _Service::Stop _SVC _FSR _ERR1 _ERR2
    StrCmpS $Bit 64 0 +4
    StrCmp "${_FSR}" /DISABLEFSR 0 +3
    ExecDos::Exec /TOSTACK /DISABLEFSR `"${SC}" stop "${_SVC}"`
    Goto +2
    ExecDos::Exec /TOSTACK `"${SC}" stop "${_SVC}"`
    Pop ${_ERR1}
    Pop ${_ERR2}
!macroend

Service::Remove

Discription
This macro will simply delete a specified service. Can be used for both local and portable versions of services.
;=#
; ${Service::Remove} "NAME" /DISABLEFSR $0 $1
;
;    ::Remove    = Deletes a service entry from the registry. 
;    NAME        = The Service name
;    /DISABLEFSR = Disables redirection if x64. Use "" to skip.
;    $0          = Return after call
;    $1          =   ''    ''    ''
;
; Be sure to stop a service first if it's running.
;
!define Service::Remove `!insertmacro _Service::Remove`
!macro _Service::Remove _SVC _FSR _ERR1 _ERR2
    StrCmpS $Bit 64 0 +4
    StrCmp "${_FSR}" /DISABLEFSR 0 +3
    ExecDos::Exec /TOSTACK /DISABLEFSR `"${SC}" delete "${_SVC}"`
    Goto +2
    ExecDos::Exec /TOSTACK `"${SC}" delete "${_SVC}"`
    Pop ${_ERR1}
    Pop ${_ERR2}
!macroend

Service::Create

Discription
This macro will create a new service for both local and portable use. If the service requires multiple dependancies than use a forward slash (/) to seperate them.
;=#
; ${Service::Create} "NAME" "PATH" "TYPE" "START" "DEPEND" /DISABLEFSR $0 $1
;
;    ::Create    = Creates a service entry in the registry and Service Database
;    NAME        = The Service name
;    PATH        = BinaryPathName to the .exe file
;    TYPE        = own|share|interact|kernel|filesys|rec
;    START       = boot|system|auto|demand|disabled|delayed-auto
;    DEPEND      = Dependencies(separated by / (forward slash))
;    /DISABLEFSR = Disables redirection if x64. Use "" to skip.
;    $0          = Return after call
;    $1          =   ''    ''    ''
;
!define Service::Create `!insertmacro _Service::Create`
!macro _Service::Create _SVC _PATH _TYPE _START _DEPEND _FSR _ERR1 _ERR2
	StrCmpS $Bit 64 0 +7
	StrCmp "${_FSR}" /DISABLEFSR 0 +6
	StrCmp "${_DEPEND}" "" 0 +3
	ExecDos::Exec /TOSTACK /DISABLEFSR `"${SC}" create "${_SVC}" DisplayName= "${FULLNAME}" binpath= "${_PATH}" type= "${_TYPE}" start= "${_START}"`
	Goto +7
	ExecDos::Exec /TOSTACK /DISABLEFSR `"${SC}" create "${_SVC}" DisplayName= "${FULLNAME}" binpath= "${_PATH}" type= "${_TYPE}" start= "${_START}" depend= ""${_DEPEND}""`
	Goto +5
	StrCmp "${_DEPEND}" "" 0 +3
	ExecDos::Exec /TOSTACK `"${SC}" create "${_SVC}" DisplayName= "${FULLNAME}" binpath= "${_PATH}" type= "${_TYPE}" start= "${_START}"`
	Goto +2
	ExecDos::Exec /TOSTACK `"${SC}" create "${_SVC}" DisplayName= "${FULLNAME}" binpath= "${_PATH}" type= "${_TYPE}" start= "${_START}" depend= ""${_DEPEND}""`
	Pop ${_ERR1}
	Pop ${_ERR2}
!macroend

Example Usage

Discription
Below is an example custom.nsh file with the bare essantials which will show how to use these service macros in action.
;= VARIABLES
;= ################
Var SVCBinary

;= DEFINES
;= ################
!define SVC			`SomeServiceName`
!define SVCKEY		SYSTEM\CurrentControlSet\services\${SVC}
!define HKLM		HKLM\${SVCKEY}
!define SC			`$SYSDIR\sc.exe`

;= MACROS
;= ################
!define Service::Query `!insertmacro _Service::Query`
!macro _Service::Query _SVC _FSR _ERR1 _ERR2
	StrCmpS $Bit 64 0 +4
	StrCmp "${_FSR}" /DISABLEFSR 0 +3
	ExecDos::Exec /TOSTACK /DISABLEFSR `"${SC}" query "${_SVC}"`
	Goto +2
	ExecDos::Exec /TOSTACK `"${SC}" query "${_SVC}"`
	Pop ${_ERR1}
	Pop ${_ERR2}
	StrCmp ${_ERR1} "1060" 0 +3
	SetErrors
	Goto +2
	${WriteRuntimeData} "${_SVC}Service" LocalService true
!macroend
!define Service::QueryConfig `!insertmacro _Service::QueryConfig`
!macro _Service::QueryConfig _SVC _FSR _ERR1 _ERR2
	ReadEnvStr $R0 COMSPEC
	StrCmpS $Bit 64 0 +4
	StrCmp "${_FSR}" /DISABLEFSR 0 +3
	ExecDos::Exec /TOSTACK /DISABLEFSR `"$R0" /c "${SC} qc "${_SVC}" | FIND "BINARY_PATH_NAME""`
	Goto +2
	ExecDos::Exec /TOSTACK `"$R0" /c "${SC} qc "${_SVC}" | FIND "BINARY_PATH_NAME""`
	Pop ${_ERR1}
	Pop ${_ERR2}
	StrCmpS ${_ERR1} 0 0 +4
	StrCpy $R1 ${_ERR2} "" 29
	${WriteRuntimeData} "${_SVC}Service" LocalPath "$R1"
	ReadRegStr $R1 HKLM `SYSTEM\CurrentControlSet\services\${_SVC}` ImagePath
	${WriteRuntimeData} "${_SVC}Service" LocalPath "$R1"
!macroend
!define Service::State `!insertmacro _Service::State`
!macro _Service::State _SVC _FSR _ERR1 _ERR2
    ReadEnvStr $R0 COMSPEC
    StrCmpS $Bit 64 0 +4
    StrCmp "${_FSR}" /DISABLEFSR 0 +3
    ExecDos::Exec /TOSTACK /DISABLEFSR `"$R0" /c "${SC} query "${_SVC}" | find /C "RUNNING""`
    Goto +2
    ExecDos::Exec /TOSTACK `"$R0" /c "${SC} query "${_SVC}" | find /C "RUNNING""`
    Pop ${_ERR1}
    Pop ${_ERR2}
	StrCmpS ${_ERR1} 1 0 +4
	StrCmpS ${_ERR2} 1 0 +3
	${WriteRuntimeData} "${_SVC}Service" LocalState Running
!macroend
!define Service::Start `!insertmacro _Service::Start`
!macro _Service::Start _SVC _FSR _ERR1 _ERR2
    StrCmpS $Bit 64 0 +4
    StrCmp "${_FSR}" /DISABLEFSR 0 +3
    ExecDos::Exec /TOSTACK /DISABLEFSR `"${SC}" start "${_SVC}"`
    Goto +2
    ExecDos::Exec /TOSTACK `"${SC}" start "${_SVC}"`
    Pop ${_ERR1}
    Pop ${_ERR2}
!macroend
!define Service::Stop `!insertmacro _Service::Stop`
!macro _Service::Stop _SVC _FSR _ERR1 _ERR2
    StrCmpS $Bit 64 0 +4
    StrCmp "${_FSR}" /DISABLEFSR 0 +3
    ExecDos::Exec /TOSTACK /DISABLEFSR `"${SC}" stop "${_SVC}"`
    Goto +2
    ExecDos::Exec /TOSTACK `"${SC}" stop "${_SVC}"`
    Pop ${_ERR1}
    Pop ${_ERR2}
!macroend
!define Service::Remove `!insertmacro _Service::Remove`
!macro _Service::Remove _SVC _FSR _ERR1 _ERR2
    StrCmpS $Bit 64 0 +4
    StrCmp "${_FSR}" /DISABLEFSR 0 +3
    ExecDos::Exec /TOSTACK /DISABLEFSR `"${SC}" delete "${_SVC}"`
    Goto +2
    ExecDos::Exec /TOSTACK `"${SC}" delete "${_SVC}"`
    Pop ${_ERR1}
    Pop ${_ERR2}
!macroend
!define Service::Create `!insertmacro _Service::Create`
!macro _Service::Create _SVC _PATH _TYPE _START _DEPEND _FSR _ERR1 _ERR2
	StrCmpS $Bit 64 0 +7
	StrCmp "${_FSR}" /DISABLEFSR 0 +6
	StrCmp "${_DEPEND}" "" 0 +3
	ExecDos::Exec /TOSTACK /DISABLEFSR `"${SC}" create "${_SVC}" DisplayName= "${FULLNAME}" binpath= "${_PATH}" type= "${_TYPE}" start= "${_START}"`
	Goto +7
	ExecDos::Exec /TOSTACK /DISABLEFSR `"${SC}" create "${_SVC}" DisplayName= "${FULLNAME}" binpath= "${_PATH}" type= "${_TYPE}" start= "${_START}" depend= ""${_DEPEND}""`
	Goto +5
	StrCmp "${_DEPEND}" "" 0 +3
	ExecDos::Exec /TOSTACK `"${SC}" create "${_SVC}" DisplayName= "${FULLNAME}" binpath= "${_PATH}" type= "${_TYPE}" start= "${_START}"`
	Goto +2
	ExecDos::Exec /TOSTACK `"${SC}" create "${_SVC}" DisplayName= "${FULLNAME}" binpath= "${_PATH}" type= "${_TYPE}" start= "${_START}" depend= ""${_DEPEND}""`
	Pop ${_ERR1}
	Pop ${_ERR2}
!macroend

;= CUSTOM 
;= ################
${SegmentFile}
${SegmentPre} ;= Uninstall Local Services
	ClearErrors
	${Service::Query} ${SVC} /DISABLEFSR $0 $1
	IfErrors NONE LOCAL
LOCAL:
	${Service::QueryConfig} ${SVC} /DISABLEFSR $0 $1
	${Registry::CopyKey} `${HKLM}` `${PAF}\Keys\${HKLM}` $0
	${Service::State} ${SVC} /DISABLEFSR $0 $1
	${If} $0 == 1
	${AndIf} $1 == 1
		${Service::Stop} ${SVC} /DISABLEFSR $0 $1
	${EndIf}
	${Service::Remove} ${SVC} /DISABLEFSR $0 $1
NONE:
	ClearErrors
!macroend
${SegmentPrePrimary} ;= Install Portable Services
	${If} $Bit == 64
		StrCpy $SVCBinary "$EXEDIR\App\ProgramDir\service64.exe"
	${Else}
		StrCpy $SVCBinary "$EXEDIR\App\ProgramDir\service32.exe"
	${EndIf}
	${Service::Create} ${SVC} `$SVCBinary` own demand "" /DISABLEFSR $0 $1
	${Registry::CopyKey} `${PAFKEYS}\${HKLM}` `${HKLM}` $0
	WriteRegStr HKLM "${SVCKEY}" "ImagePath" "$SVCBinary"
	Sleep 50
	${Service::Start} ${SVC} /DISABLEFSR $0 $1
!macroend
${SegmentPostPrimary} ;= Uninstall Portable Services
	${Service::Stop} ${SVC} /DISABLEFSR $0 $1
	Sleep 50
	${Service::Remove} ${SVC} /DISABLEFSR $0 $1
	DeleteRegKey HKLM `${SVCKEY}`
!macroend
${SegmentUnload} ;= Restore Local Services
	${ReadRuntimeData} $0 "${SVC}Service" LocalService
	${IfNot} ${Errors}
	${AndIf} $0 == true
		${Registry::RestoreBackupKey} `${HKLM}` $0
		${ReadRuntimeData} $R0 "${SVC}Service" LocalPath
		${IfNot} ${Errors}
			${Service::Create} ${SVC} `$R0` own demand "" /DISABLEFSR $0 $1
			Sleep 50
			${Service::Start} ${SVC} /DISABLEFSR $1 $2
		${EndIf}
	${EndIf}
!macroend
ServiceLib.nsh
Discription
The following is some extra macros I've written for use within the custom.nsh file. Please note that in order to use these macros below you must include the ServiceLib.nsh header file as these macros makes use of it's function.
!define ServiceLib::Create `!insertmacro _ServiceLib::Create`
!macro _ServiceLib::Create _RETURN _NAME _PATH _TYPE _START _DEPEND
	Push "start"
	Push "${_NAME}"
	StrCmp "${_DEPEND}" "" 0 +3
	Push "path=${_PATH};servicetype=${_TYPE};starttype=${_START};"
	Goto +2
	Push "path=${_PATH};servicetype=${_TYPE};starttype=${_START};depend=${_DEPEND};"
	Call Service
	Pop ${_RETURN}
!macroend
!define ServiceLib::Start `!insertmacro _ServiceLib::Start`
!macro _ServiceLib::Start _RETURN _NAME
	Push "start"
	Push "${_NAME}"
	Push ""
	Call Service
	Pop ${_RETURN} ;= Returns true/false
!macroend
!define ServiceLib::Remove `!insertmacro _ServiceLib::Remove`
!macro _ServiceLib::Remove _RETURN _NAME
	Push "delete"
	Push "${_NAME}"
	Push ""
	Call Service
	Pop ${_RETURN} ;= Returns true/false
!macroend
!define ServiceLib::Stop `!insertmacro _ServiceLib::Stop`
!macro _ServiceLib::Stop _RETURN _NAME
	Push "stop"
	Push "${_NAME}"
	Push ""
	Call Service
	Pop ${_RETURN} ;= Returns true/false
!macroend
!define ServiceLib::Pause `!insertmacro _ServiceLib::Pause`
!macro _ServiceLib::Pause _RETURN _NAME
	Push "pause"
	Push "${_NAME}"
	Push ""
	Call Service
	Pop ${_RETURN} ;= Returns true/false
!macroend
!define ServiceLib::Continue `!insertmacro _ServiceLib::Continue`
!macro _ServiceLib::Continue _RETURN _NAME
	Push "continue"
	Push "${_NAME}"
	Push ""
	Call Service
	Pop ${_RETURN} ;= Returns true/false
!macroend
!define ServiceLib::Status `!insertmacro _ServiceLib::Status`
!macro _ServiceLib::Status _RETURN _NAME
	Push "status"
	Push "${_NAME}"
	Push ""
	Call Service
	Pop ${_RETURN} ;= Returns stopped/running/start_pending/stop_pending/continue_pending/pause_pending/pause/unknown
!macroend
Information
For information about the ServiceLib.nsh file or to get the latest copy you can visit this page.