-
Notifications
You must be signed in to change notification settings - Fork 15
Windows 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 every time 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.
Tip
With Pac-Man, you no longer need to use the custom.nsh file to handle Windows 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.
Tip
In Pac-Man, you would use the information 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 Pac-Man to PAF your portable, then you should have inside your Launcher.ini file something similar 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
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 its original condition in ${SegmentUnload}.
Important
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.
This macro will check the existence of a service. If the specified service is found it will then write in the RuntimeData.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
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 its location in the RuntimeData.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
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 RuntimeData.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
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
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
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
This macro will create a new service for both local and portable use. If the service requires multiple dependencies than use a forward slash (/) to separate 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
Below is an example custom.nsh file with the bare essentials 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
Launcher.ini Config Examples