How to stop a Win32 service started with Win32::Daemon?

喜你入骨 提交于 2020-07-09 03:28:36

问题


I am able to start a Win32 service successfully on Windows 10 (Strawberry perl version 5.30.1) using the follwing script:

package Win32::XYZService;
use feature qw(say);
use strict;
use warnings;
use File::Spec;
use Win32;
use Win32::Daemon;

{
    die "Bad arguments" if @ARGV != 1;
    my $action = shift @ARGV;
    my $xyz = Win32::XYZService->new();
    $xyz->action( $action );

}

sub new {
    my ( $class, %args ) = @_;
    $args{name} = 'xyz_service2';
    my ($bin, $scriptname) = Win32::GetFullPathName( $0 );
    $args{bin} = $bin;
    $args{scriptname} = $scriptname;
    $args{log_fn} = File::Spec->catfile( $bin, 'log.txt' );
    $args{time_interval} = 2000;  # callback timer interval in milliseconds
    my $self = bless \%args, $class;
    return $self;
}

sub action {
    my ($self, $action) = @_;

    if ($self->can($action)) {
        return $self->$action();
    }
    else {
        $self->log("Unknown command: $action");
        $self->log("Valid commands are: create, start, stop, delete");
        return undef;
    }
}

sub start {
    my ($self) = @_;
    $self->log("starting service..");
    system("net", "start", $self->{name});
}

sub stop {
    my ($self) = @_;

    $self->log("trying to stop service..");
    system("net", "stop", $self->{name});
}

sub _scm_start {
    my ($self) = @_;

    Win32::Daemon::RegisterCallbacks( {
        start       =>  \&_callback_start,
        timer       =>  \&_callback_timer,
        stop        =>  \&_callback_stop,
        pause       =>  \&_callback_pause,
        continue    =>  \&_callback_continue,
    } );
    Win32::Daemon::StartService( $self, $self->{time_interval} );
}

sub _callback_continue {
    my ( $event, $self) = @_;

    $self->log("callback continue");
    Win32::Daemon::State( SERVICE_RUNNING );
}

sub _callback_pause {
    my ( $event, $self) = @_;

    $self->log("callback pause");
    Win32::Daemon::State( SERVICE_PAUSED );
}

sub _callback_stop {
    my ( $event, $self) = @_;

    $self->log("callback stop");
    Win32::Daemon::State( SERVICE_STOPPED );
    Win32::Daemon::StopService();
}

sub _callback_timer {
    my ( $event, $self) = @_;

    $self->log("callback timer");
}

sub _callback_start {
    my ( $event, $self) = @_;

    $self->log("callback start");
    Win32::Daemon::State( SERVICE_RUNNING );
}

sub log {
    my ($self, $msg) = @_;

    chomp $msg;
    open(my $fh, ">>", $self->{log_fn})
        or warn("Can't append to log \"$self->{log_fn}\": $!\n"), return;
    say $fh "[PID $$] [" . localtime . "] : $msg";
    say $msg if -t STDIN;
}

sub delete {
    my ($self) = @_;
    if (Win32::Daemon::DeleteService("", $self->{name})) {
        $self->log("Successfully removed service $self->{name}");
    }
    else {
        $self->log("Failed to remove service: " . Win32::FormatMessage( Win32::Daemon::GetLastError()));
    }
}

sub create {
    my ($self) = @_;
    my $service_path = $^X;
    my $service_params = File::Spec->catfile($self->{bin}, $self->{scriptname});
    $service_params .= ' _scm_start';  # Service control manager start
    my %service_info = (
        name            => $self->{name},
        display         => 'xyz_display',
        path            => $service_path,
        description     => 'xyz_description',
        parameters      => $service_params,
        service_type    => SERVICE_WIN32_OWN_PROCESS,
        start_type      => SERVICE_AUTO_START
    );

    if (Win32::Daemon::CreateService( \%service_info)) {
        $self->log("Successfully added service $service_info{name}");
    }
    else {
        $self->log("Failed to add service: " . Win32::FormatMessage( Win32::Daemon::GetLastError()));
    }
}

If I run this script from command prompt as admin:

>perl xyz_service.pl create
Successfully added service xyz_service2

>perl xyz_service.pl start
starting service..
The xyz_display service is starting.
The xyz_display service was started successfully.

>type log.txt
[PID 8844] [Wed Jul  1 11:33:05 2020] : Successfully added service xyz_service2
[PID 10552] [Wed Jul  1 11:33:42 2020] : starting service..
[PID 12076] [Wed Jul  1 11:33:42 2020] : callback start
[PID 12076] [Wed Jul  1 11:33:44 2020] : callback timer
[PID 12076] [Wed Jul  1 11:33:46 2020] : callback timer
[PID 12076] [Wed Jul  1 11:33:48 2020] : callback timer
[PID 12076] [Wed Jul  1 11:33:50 2020] : callback timer
[PID 12076] [Wed Jul  1 11:33:52 2020] : callback timer
[PID 12076] [Wed Jul  1 11:33:54 2020] : callback timer

>perl xyz_service.pl stop
stopping service..
The requested pause, continue, or stop is not valid for this service.
More help is available by typing NET HELPMSG 2191.

How can I stop the service?


回答1:


The module is supposed to use SetServiceStatus to signal to Windows that it can handle shutdown events. And it does so in older versions of Windows. However, it fails to do that in Windows 10 (and newer) and Windows Server 2016 (and newer).

This failure is the result of the lack of a default clause in a switch statement in the module's DllMain in Demon.xs. As a result of this problem, gdwControlsAccepted ends up with an incorrect value.

The corrected switch:

            switch( gsOSVerInfo.dwMajorVersion )
            {
                default:
                    // We have Windows Vista or newer
                    //  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;
            }

I have not tested this. Please test and file bug report.



来源:https://stackoverflow.com/questions/62674009/how-to-stop-a-win32-service-started-with-win32daemon

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!