perlscheduled-taskswin32ole

Windows Task Scheduler from Perl with Win32::OLE - Invalid Query


I am trying to use Win32::OLE to create a task on a Windows 10 system, something similar to SetDefaultPrinterFromSSD.ps1. I tried to manually validate the query by creating the same task by hand in the task scheduler GUI, and that seemed to work.

Here's my test code:

use File::Basename;
use Win32;
use Win32::OLE; 
$Win32::OLE::Warn = 3; 
use Data::Dumper; 

my ($me, $dirpath, $suffix) = fileparse($0, qr/\.[^.]*/);
my ($system, $login, $domain, $sidbin, $sidtype, $sidtxt) = "";
$login = Win32::LoginName();
Win32::LookupAccountName($system, $login, $domain, $sidbin, $sidtype);

my($Revision, $SubAuthorityCount,@IdentifierAuthorities) = unpack("CCnnn", $sidbin);
unless (($IdentifierAuthorities[0] || $IdentifierAuthorities[1])) {
    my($temp, $temp2, @SubAuthorities) =     unpack("VVV$SubAuthorityCount",$sidbin);

    $sidtxt = "S-$Revision-$IdentifierAuthorities[2]-".join("-",@SubAuthorities);
}

die Win32::OLE->LastError() unless (my $service = Win32::OLE->CreateObject('Schedule.Service'));
$service->Connect;

my $RootFolder = $service->GetFolder('\\');
die Win32::OLE->LastError() unless (my $TaskDefinition = $service->NewTask(0));

die Win32::OLE->LastError() unless (my $regInfo = $TaskDefinition->RegistrationInfo);
$regInfo->{Description} = "Register a perl task as an event $me";
$regInfo->{Author} = "$domain\\$login";
$regInfo->{URI} = "$sidtxt\\$me";

die Win32::OLE->LastError() unless (my $settings = $TaskDefinition->Settings);
$settings->{Enabled} = 1;
$settings->{AllowDemandStart} = 1;
$settings->{DisallowStartIfOnBatteries} = 0;
$settings->{StopIfGoingOnBatteries} = 0;
$settings->{Hidden} = 0;

my @Triggers;
my $TriggerSet;
die Win32::OLE->LastError() unless ($TriggerSet = $TaskDefinition->Triggers);
for (10000..10001) {
    die Win32::OLE->LastError() unless (push @Triggers, $TriggerSet->Create(0));
    $Triggers[$#Triggers]->{Id} = $_;
    $Triggers[$#Triggers]->{Subscription} = 
        "<QueryList>
          <Query Id=\"event$_\" Path=\"Microsoft-Windows-NetworkProfile/Operational\">
            <Select Path=\"Microsoft-Windows-NetworkProfile/Operational\">*[System[(EventID=\"$_\")]]</Select>
          </Query>
        </QueryList>";
    die Win32::OLE->LastError() 
  unless (my $values = $Triggers[$#Triggers]->ValueQueries->Create("eventId", "Event/System/EventID"));
    $Triggers[$#Triggers]->{Enabled} = 1;
}

die Win32::OLE->LastError() unless (my $Action = $TaskDefinition->Actions()->Create(0));
$Action->{Path} = 'C:\Perl64\Bin\Perl.exe';
$Action->{Arguments} = "$0 -f event\${eventID}";

$RootFolder->RegisterTaskDefinition("OLE-Test",$TaskDefinition,6,undef,undef,3);
print Dumper $TaskDefinition->{XmlText};

If I run the code with RegisterTaskDefinition with TASK_VALIDATE_ONLY flag set (third parameter = 1), I get a nice XML dump. So far so good. When I run the code with RegisterTaskDefinition with TASK_CREATE_OR_UPDATE (third parameter = 6), I get this error:

    OLE exception from "<Unknown Source>":

    (11,263):Subscription:<QueryList><Query Id="event10000"
    Path="Microsoft-Windows-NetworkProfile/Operational"><Select
    Path="Microsoft-Windows-NetworkProfile/Operational">* 
   [System[(EventID="10000")]]</Select></Query></QueryList>

    Win32::OLE(0.1712) error 0x80073a99: "The specified query is invalid"
        in METHOD/PROPERTYGET "RegisterTaskDefinition" at OLE-test.pl line 63.

Anyone familiar enough with Win32::OLE, and the Windows task scheduler XML to explain what I'm doing wrong?


Solution

  • The Query Id attribute must be numeric. I got the following to work:

    for (10000..10001) {
        die Win32::OLE->LastError() unless (push @Triggers, $TriggerSet->Create(0));
        $Triggers[$#Triggers]->{Id} = $_;
        $Triggers[$#Triggers]->{Subscription} =
            qq{<QueryList>
              <Query Id="$_" Path="Microsoft-Windows-NetworkProfile/Operational">
                <Select Path="Microsoft-Windows-NetworkProfile/Operational">*[System[(EventID="$_")]]</Select>
              </Query>
            </QueryList>};
        die Win32::OLE->LastError() 
      unless (my $values = $Triggers[$#Triggers]->ValueQueries->Create("eventId", "Event/System/EventID"));
        $Triggers[$#Triggers]->{Enabled} = 1;
    }
    

    The relevant, magic change is matching

        $Triggers[$#Triggers]->{Id} = $_;
    

    and

        <Query Id="$_" Path="Microsoft-Windows-NetworkProfile/Operational">
    

    Maybe you can change both to be non-numeric, but with that change, I created tasks that I was able to view afterwards.

    Consider using $^X in your code to make the path to Perl more dynamic instead of hardcoding it to C:\Perl64.