WinService.pm 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. ################################################################
  2. #
  3. # Copyright notice
  4. #
  5. # (c) 2013 Thomas Eckardt (Thomas.Eckardt@thockar.com)
  6. #
  7. # This script is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # The GNU General Public License can be found at
  13. # http://www.gnu.org/copyleft/gpl.html.
  14. # A copy is found in the textfile GPL.txt and important notices to the license
  15. # from the author is found in LICENSE.txt distributed with these scripts.
  16. #
  17. # This script is distributed in the hope that it will be useful,
  18. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. # GNU General Public License for more details.
  21. #
  22. # This copyright notice MUST APPEAR in all copies of the script!
  23. #
  24. ################################################################
  25. # $Id: WinService.pm 5819 2014-05-11 14:17:21Z rudolfkoenig $
  26. package FHEM::WinService;
  27. use strict;
  28. sub __installService($$$);
  29. sub __initService($$);
  30. sub new($$$);
  31. use vars qw($VERSION);
  32. $VERSION = $1 if('$Id: WinService.pm 5819 2014-05-11 14:17:21Z rudolfkoenig $' =~ /,v ([\d.]+) /);
  33. ###################################################
  34. # Windows Service Handler
  35. #
  36. # install/remove or possibly start fhem as a Windows Service
  37. sub
  38. new ($$$) {
  39. my ($class, $argv) = @_;
  40. my $fhem = $0;
  41. $fhem =~ s/\\/\//go;
  42. my $fhembase = $fhem;
  43. $fhembase =~ s/\/?[^\/]+$//o;
  44. if (! $fhembase && eval('use Cwd();1;')) {
  45. $fhembase = Cwd::cwd();
  46. $fhembase =~ s/\\/\//go;
  47. }
  48. my $larg = $argv->[@$argv-1];
  49. if ($larg eq '-i') {
  50. if (! $fhembase) {
  51. print "error: unable to detect fhem folder - cancel\n";
  52. exit 0;
  53. }
  54. $fhem = $fhembase.'/'.$fhem if $fhem !~ /\//o;
  55. my $cfg = $argv->[0];
  56. $cfg =~ s/\\/\//go;
  57. $cfg = $fhembase.'/'.$cfg if $cfg !~ /\//o;
  58. print "try to install fhem windows service as: $^X $fhem $cfg\n";
  59. __installService('-i' , $fhem, $cfg);
  60. exit 0;
  61. } elsif ($larg eq '-u') {
  62. print "try to remove fhem windows service\n";
  63. __installService('-u',undef,undef);
  64. exit 0;
  65. } else {
  66. $class = ref $class || $class;
  67. bless my $self = {}, $class;
  68. $self->{ServiceLog} = [];
  69. @{$self->{ServiceLog}} = __initService($self, $argv->[0]);
  70. return $self;
  71. }
  72. }
  73. ###################################################
  74. ###################################################
  75. # from here are internal subs only !
  76. ###################################################
  77. ###################################################
  78. # install or remove fhem as a Windows Service
  79. #
  80. sub
  81. __installService($$$)
  82. {
  83. eval(<<'EOT') or print "error: $@\n)";
  84. use Win32::Daemon;
  85. my $p;
  86. my $p2;
  87. if(lc $_[0] eq '-u') {
  88. system('cmd.exe /C net stop fhem');
  89. sleep(1);
  90. Win32::Daemon::DeleteService('','fhem') ||
  91. print "Failed to remove fhem service: " .
  92. Win32::FormatMessage( Win32::Daemon::GetLastError() ) . "\n" & return;
  93. print "Successfully removed service fhem\n";
  94. } elsif( lc $_[0] eq '-i') {
  95. unless($p=$_[1]) {
  96. $p=$0;
  97. $p=~s/\w+\.pl/fhem.pl/o;
  98. }
  99. if($p2=$_[2]) {
  100. $p2=~s/[\\\/]$//o;
  101. } else {
  102. $p2=$p; $p2=~s/\.pl/.cfg/io;
  103. }
  104. my %Hash = (
  105. name => 'fhem',
  106. display => 'fhem server',
  107. path => "\"$^X\"",
  108. user => '',
  109. pwd => '',
  110. parameters => "\"$p\" \"$p2\"",
  111. );
  112. if( Win32::Daemon::CreateService( \%Hash ) ) {
  113. print "fhem service successfully added.\n";
  114. } else {
  115. print "Failed to add fhem service: " .
  116. Win32::FormatMessage( Win32::Daemon::GetLastError() ) . "\n";
  117. print "Note: if you're getting an error: Service is marked for ".
  118. "deletion, then close the service control manager window".
  119. " and try again.\n";
  120. }
  121. }
  122. 1;
  123. EOT
  124. }
  125. ###################################################
  126. # check if called from SCM and start the service if so
  127. #
  128. sub
  129. __initService ($$) {
  130. my ($self, $arg) = @_;
  131. my @ServiceLog;
  132. # check how we are called from the OS - Console or SCM
  133. # Win32 Daemon and Console module installed ?
  134. if( eval("use Win32::Daemon; use Win32::Console; 1;") ) {
  135. eval(<<'EOT');
  136. my $cmdlin = Win32::Console::_GetConsoleTitle () ? 1 : 0;
  137. eval{&main::doGlobalDef($arg);}; # we need some config here
  138. if ($cmdlin) {
  139. $self->{AsAService} = 0;
  140. } else {
  141. $self->{AsAService} = 1;
  142. $self->{ServiceStopping} = 0;
  143. $main::attr{global}{nofork}=1; # this has to be set here
  144. push @ServiceLog, 'registering fhem as Windows Service';
  145. Win32::Daemon::StartService();
  146. # Wait until the service manager is ready for us to continue...
  147. my $i = 0;
  148. while( SERVICE_START_PENDING != Win32::Daemon::State() && $i < 60) {
  149. # looping indefinitely and waiting to start
  150. sleep( 1 );
  151. $i++;
  152. }
  153. if ($i > 59) {
  154. push @ServiceLog,'unable to register fhem in SCM - cancel';
  155. die "unable to register fhem in SCM - cancel\n";
  156. }
  157. Win32::Daemon::State( SERVICE_RUNNING );
  158. push @ServiceLog,'starting fhem as a service';
  159. # this sub is called in the main loop to check the service state
  160. $self->{serviceCheck} = sub {
  161. return unless $self->{AsAService};
  162. my $state = Win32::Daemon::State();
  163. my %idlestate = (
  164. Win32::Daemon::SERVICE_PAUSE_PENDING => 1,
  165. Win32::Daemon::SERVICE_CONTINUE_PENDING => -1
  166. );
  167. if( $state == SERVICE_STOP_PENDING ) {
  168. if ($self->{ServiceStopping} == 0) {
  169. $self->{ServiceStopping} = 1;
  170. &main::Log(1,'service stopping');
  171. #ask SCM for a grace time (30 seconds) to shutdown
  172. Win32::Daemon::State( SERVICE_STOP_PENDING, 30000 );
  173. &main::Log(1, 'service stopped');
  174. &main::CommandShutdown(undef, undef);
  175. $self->{ServiceStopping} = 2;
  176. Win32::Daemon::State( SERVICE_STOPPED );
  177. Win32::Daemon::StopService();
  178. # be nice, tell we stopped
  179. exit 0;
  180. } elsif ($self->{ServiceStopping} == 1) {
  181. # keep telling SCM we're stopping and didn't hang
  182. Win32::Daemon::State( SERVICE_STOP_PENDING, 30000 );
  183. }
  184. } elsif ( $state == SERVICE_PAUSE_PENDING ) {
  185. Win32::Daemon::State( SERVICE_PAUSED );
  186. $self->{allIdle} = $idlestate{$state};
  187. &main::Log(1,'pausing service');
  188. } elsif ( $state == SERVICE_CONTINUE_PENDING ) {
  189. Win32::Daemon::State( SERVICE_RUNNING );
  190. $self->{allIdle} = $idlestate{$state};
  191. &main::Log(1, 'continue service');
  192. } else {
  193. my $PrevState = SERVICE_RUNNING;
  194. $PrevState = SERVICE_STOPPED if $self->{ServiceStopping};
  195. $PrevState = SERVICE_PAUSED if $self->{allIdle} > 0;
  196. Win32::Daemon::State( $PrevState );
  197. undef $self->{allIdle}
  198. if ($PrevState == SERVICE_RUNNING);
  199. }
  200. };
  201. } # end if ($cmdlin)
  202. EOT
  203. if ($@) { # we got some Perl errors in eval
  204. push @ServiceLog, "error: $@";
  205. $self->{serviceCheck} = sub {}; # set it - could be destroyed
  206. $self->{AsAService} = 0;
  207. }
  208. push @ServiceLog,'starting in console mode'
  209. unless $self->{AsAService};
  210. } else {
  211. $self->{AsAService} = 0;
  212. push @ServiceLog,'starting in console mode';
  213. }
  214. return @ServiceLog;
  215. }
  216. ###################################################
  217. 1;