mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-28 21:28:55 +00:00
Bug 1676296 Part 1: XPCOM component for Windows Task Scheduler. r=mhowell
Differential Revision: https://phabricator.services.mozilla.com/D97841
This commit is contained in:
parent
215e494a20
commit
6443deaaee
@ -110,6 +110,9 @@ DIRS += ["captivedetect"]
|
||||
if CONFIG["OS_TARGET"] != "Android":
|
||||
DIRS += ["terminator"]
|
||||
|
||||
if CONFIG["MOZ_UPDATE_AGENT"]:
|
||||
DIRS += ["taskscheduler"]
|
||||
|
||||
DIRS += ["build"]
|
||||
|
||||
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
|
||||
|
18
toolkit/components/taskscheduler/components.conf
Normal file
18
toolkit/components/taskscheduler/components.conf
Normal file
@ -0,0 +1,18 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
Classes = []
|
||||
|
||||
if buildconfig.substs['OS_TARGET'] == 'WINNT':
|
||||
Classes += [
|
||||
{
|
||||
'cid': '{e2113dfc-8efe-43a1-8a20-ad720dd771d6}',
|
||||
'contract_ids': ['@mozilla.org/win-task-scheduler-service;1'],
|
||||
'type': 'nsWinTaskSchedulerService',
|
||||
'headers': ['nsWinTaskScheduler.h'],
|
||||
'processes': ProcessSelector.MAIN_PROCESS_ONLY,
|
||||
},
|
||||
]
|
23
toolkit/components/taskscheduler/moz.build
Normal file
23
toolkit/components/taskscheduler/moz.build
Normal file
@ -0,0 +1,23 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
with Files("**"):
|
||||
BUG_COMPONENT = ("Toolkit", "Application Update")
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell/xpcshell.ini"]
|
||||
|
||||
XPIDL_MODULE = "taskscheduler"
|
||||
XPCOM_MANIFESTS += ["components.conf"]
|
||||
|
||||
# This whole component is currently Windows-only, but a Mac implementation is planned.
|
||||
# Only the Windows C++ interface is an XPCOM component.
|
||||
if CONFIG["OS_TARGET"] == "WINNT":
|
||||
XPIDL_SOURCES += ["nsIWinTaskSchedulerService.idl"]
|
||||
EXPORTS += ["nsWinTaskScheduler.h"]
|
||||
SOURCES += ["nsWinTaskScheduler.cpp"]
|
||||
OS_LIBS += ["taskschd"]
|
||||
|
||||
FINAL_LIBRARY = "xul"
|
104
toolkit/components/taskscheduler/nsIWinTaskSchedulerService.idl
Normal file
104
toolkit/components/taskscheduler/nsIWinTaskSchedulerService.idl
Normal file
@ -0,0 +1,104 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
/**
|
||||
* An interface for Windows Task Scheduler 2.0.
|
||||
* Documentation for the underlying APIs can be found at
|
||||
* https://docs.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-start-page
|
||||
*/
|
||||
[scriptable, main_process_scriptable_only, uuid(a8d36901-0b6a-46c3-a214-a9e1d5d6047a)]
|
||||
interface nsIWinTaskSchedulerService : nsISupports
|
||||
{
|
||||
/**
|
||||
* Register (create) a task from an XML definition.
|
||||
* The task will be created so that it only runs as the current user
|
||||
* (TASK_LOGON_INTERACTIVE_TOKEN).
|
||||
*
|
||||
* @throws NS_ERROR_FILE_NOT_FOUND if the folder does not exist.
|
||||
* @throws NS_ERROR_FILE_ALREADY_EXISTS if the task already existed and aUpdateExisting is false.
|
||||
*
|
||||
* @param aFolderName Full name of the folder in which to create the task, starting with "\".
|
||||
*
|
||||
* @param aTaskName Name of the task.
|
||||
*
|
||||
* @param aDefinitionXML XML definition of the task. This is passed directly to Task Scheduler,
|
||||
* see the schema at
|
||||
* https://docs.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-schema
|
||||
*
|
||||
* @param aUpdateExisting Whether to update an existing task with the same name, default false.
|
||||
*/
|
||||
void registerTask(in wstring aFolderName,
|
||||
in wstring aTaskName,
|
||||
in wstring aDefinitionXML,
|
||||
[optional] in boolean aUpdateExisting);
|
||||
|
||||
/**
|
||||
* Validate the XML task definition with Task Scheduler without creating a task, for testing.
|
||||
* Doesn't throw if only the final ITaskFolder::RegisterTask() fails.
|
||||
*
|
||||
* @param aDefinitionXML Definition to validate.
|
||||
* @return HRESULT from ITaskFolder::RegisterTask()
|
||||
* Success should be S_OK (0). XML validation failure could be one of
|
||||
* SCHED_E_UNEXPECTED_NODE, SCHED_E_NAMESPACE, SCHED_E_INVALIDVALUE,
|
||||
* SCHED_E_MISSINGNODE, SCHED_E_MALFORMEDXML, but there may be others.
|
||||
*/
|
||||
long validateTaskDefinition(in wstring aDefinitionXML);
|
||||
|
||||
/**
|
||||
* Get the registration information for a task.
|
||||
*
|
||||
* @throws NS_ERROR_FILE_NOT_FOUND if the folder or task do not exist.
|
||||
*
|
||||
* @param aFolderName Full name of the folder containing the task, starting with "\".
|
||||
* @param aTaskName Name of the task to read.
|
||||
* @return Registration information for the task, as XML text.
|
||||
*/
|
||||
AString getTaskXML(in wstring aFolderName, in wstring aTaskName);
|
||||
|
||||
/**
|
||||
* Delete a task.
|
||||
*
|
||||
* @throws NS_ERROR_FILE_NOT_FOUND if the folder or task do not exist.
|
||||
*
|
||||
* @param aFolderName Full name of the folder containing the task, starting with "\".
|
||||
* @param aTaskName Name of the task to delete.
|
||||
*/
|
||||
void deleteTask(in wstring aFolderName, in wstring aTaskName);
|
||||
|
||||
/**
|
||||
* List the names of all tasks in a task folder.
|
||||
*
|
||||
* @throws NS_ERROR_FILE_NOT_FOUND if the folder doesn't exist.
|
||||
*
|
||||
* @param aFolderName The full name of the task folder to enumerate, starting with "\".
|
||||
*
|
||||
* @return An array with the names of the tasks found.
|
||||
*/
|
||||
Array<AString> getFolderTasks(in wstring aFolderName);
|
||||
|
||||
/**
|
||||
* Create a new task subfolder under a given parent folder.
|
||||
*
|
||||
* @throws NS_ERROR_FILE_NOT_FOUND if the parent folder does not exist.
|
||||
* @throws NS_ERROR_FILE_ALREADY_EXISTS if the subfolder already exists.
|
||||
*
|
||||
* @param aParentFolderName Immediate parent for the new folder, starting with "\".
|
||||
* @param aSubFolderName Name of the new folder to create.
|
||||
*/
|
||||
void createFolder(in wstring aParentFolderName, in wstring aSubFolderName);
|
||||
|
||||
/**
|
||||
* Delete a folder.
|
||||
*
|
||||
* @throws NS_ERROR_FILE_NOT_FOUND if the parent folder does not exist.
|
||||
* @throws NS_ERROR_FILE_DIR_NOT_EMPTY if the folder was not empty.
|
||||
*
|
||||
* @param aParentFolderName Immediate parent of the folder to delete, starting with "\".
|
||||
* @param aSubFolderName Name of the folder to delete.
|
||||
*/
|
||||
void deleteFolder(in wstring aParentFolderName, in wstring aSubFolderName);
|
||||
};
|
308
toolkit/components/taskscheduler/nsWinTaskScheduler.cpp
Normal file
308
toolkit/components/taskscheduler/nsWinTaskScheduler.cpp
Normal file
@ -0,0 +1,308 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsWinTaskScheduler.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <comdef.h>
|
||||
#include <taskschd.h>
|
||||
|
||||
#include "nsString.h"
|
||||
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/ResultVariant.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
struct SysFreeStringDeleter {
|
||||
void operator()(BSTR aPtr) { ::SysFreeString(aPtr); }
|
||||
};
|
||||
using BStrPtr = mozilla::UniquePtr<OLECHAR, SysFreeStringDeleter>;
|
||||
|
||||
static nsresult ToNotFoundOrFailure(HRESULT hr) {
|
||||
if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) {
|
||||
return NS_ERROR_FILE_NOT_FOUND;
|
||||
} else {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
static nsresult ToAlreadyExistsOrFailure(HRESULT hr) {
|
||||
if (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)) {
|
||||
return NS_ERROR_FILE_ALREADY_EXISTS;
|
||||
} else {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] static Result<RefPtr<ITaskFolder>, HRESULT> GetTaskFolder(
|
||||
const char16_t* aFolderName) {
|
||||
HRESULT hr;
|
||||
RefPtr<ITaskService> scheduler = nullptr;
|
||||
|
||||
hr = CoCreateInstance(CLSID_TaskScheduler, nullptr, CLSCTX_INPROC_SERVER,
|
||||
IID_ITaskService, getter_AddRefs(scheduler));
|
||||
if (FAILED(hr)) {
|
||||
return Err(hr);
|
||||
}
|
||||
|
||||
// Connect to the local Task Scheduler.
|
||||
hr = scheduler->Connect(VARIANT{}, VARIANT{}, VARIANT{}, VARIANT{});
|
||||
if (FAILED(hr)) {
|
||||
return Err(hr);
|
||||
}
|
||||
|
||||
BStrPtr bstrFolderName =
|
||||
BStrPtr(::SysAllocString(reinterpret_cast<const OLECHAR*>(aFolderName)));
|
||||
|
||||
RefPtr<ITaskFolder> folder = nullptr;
|
||||
hr = scheduler->GetFolder(bstrFolderName.get(), getter_AddRefs(folder));
|
||||
if (FAILED(hr)) {
|
||||
return Err(hr);
|
||||
}
|
||||
|
||||
return folder;
|
||||
}
|
||||
|
||||
[[nodiscard]] static Result<RefPtr<IRegisteredTask>, HRESULT> GetRegisteredTask(
|
||||
const char16_t* aFolderName, const char16_t* aTaskName) {
|
||||
auto folder = GetTaskFolder(aFolderName);
|
||||
if (!folder.isOk()) {
|
||||
return Err(folder.unwrapErr());
|
||||
}
|
||||
|
||||
BStrPtr bstrTaskName =
|
||||
BStrPtr(::SysAllocString(reinterpret_cast<const OLECHAR*>(aTaskName)));
|
||||
|
||||
RefPtr<IRegisteredTask> task = nullptr;
|
||||
HRESULT hr =
|
||||
folder.unwrap()->GetTask(bstrTaskName.get(), getter_AddRefs(task));
|
||||
if (FAILED(hr)) {
|
||||
return Err(hr);
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsWinTaskSchedulerService, nsIWinTaskSchedulerService)
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsWinTaskSchedulerService::GetTaskXML(const char16_t* aFolderName,
|
||||
const char16_t* aTaskName,
|
||||
nsAString& aResult) {
|
||||
if (!aFolderName || !aTaskName) {
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
auto task = GetRegisteredTask(aFolderName, aTaskName);
|
||||
if (!task.isOk()) {
|
||||
return ToNotFoundOrFailure(task.unwrapErr());
|
||||
}
|
||||
|
||||
{
|
||||
BSTR bstrXml = nullptr;
|
||||
if (FAILED(task.unwrap()->get_Xml(&bstrXml))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
aResult.Assign(bstrXml, ::SysStringLen(bstrXml));
|
||||
::SysFreeString(bstrXml);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsWinTaskSchedulerService::RegisterTask(const char16_t* aFolderName,
|
||||
const char16_t* aTaskName,
|
||||
const char16_t* aDefinitionXML,
|
||||
bool aUpdateExisting) {
|
||||
if (!aFolderName || !aTaskName || !aDefinitionXML) {
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
auto folder = GetTaskFolder(aFolderName);
|
||||
if (!folder.isOk()) {
|
||||
return ToNotFoundOrFailure(folder.unwrapErr());
|
||||
}
|
||||
|
||||
BStrPtr bstrTaskName =
|
||||
BStrPtr(::SysAllocString(reinterpret_cast<const OLECHAR*>(aTaskName)));
|
||||
BStrPtr bstrXml = BStrPtr(
|
||||
::SysAllocString(reinterpret_cast<const OLECHAR*>(aDefinitionXML)));
|
||||
LONG flags = aUpdateExisting ? TASK_CREATE_OR_UPDATE : TASK_CREATE;
|
||||
TASK_LOGON_TYPE logonType = TASK_LOGON_INTERACTIVE_TOKEN;
|
||||
|
||||
// The outparam is not needed, but not documented as optional.
|
||||
RefPtr<IRegisteredTask> unusedTaskOutput = nullptr;
|
||||
HRESULT hr = folder.unwrap()->RegisterTask(
|
||||
bstrTaskName.get(), bstrXml.get(), flags, VARIANT{} /* userId */,
|
||||
VARIANT{} /* password */, logonType, VARIANT{} /* sddl */,
|
||||
getter_AddRefs(unusedTaskOutput));
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return ToAlreadyExistsOrFailure(hr);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsWinTaskSchedulerService::ValidateTaskDefinition(
|
||||
const char16_t* aDefinitionXML, int32_t* aResult) {
|
||||
if (!aDefinitionXML) {
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
auto folder = GetTaskFolder(reinterpret_cast<const char16_t*>(L"\\"));
|
||||
if (!folder.isOk()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
BStrPtr bstrXml = BStrPtr(
|
||||
::SysAllocString(reinterpret_cast<const OLECHAR*>(aDefinitionXML)));
|
||||
LONG flags = TASK_VALIDATE_ONLY;
|
||||
TASK_LOGON_TYPE logonType = TASK_LOGON_INTERACTIVE_TOKEN;
|
||||
|
||||
// The outparam is not needed, but not documented as optional.
|
||||
RefPtr<IRegisteredTask> unusedTaskOutput = nullptr;
|
||||
HRESULT hr = folder.unwrap()->RegisterTask(
|
||||
nullptr /* path */, bstrXml.get(), flags, VARIANT{} /* userId */,
|
||||
VARIANT{} /* password */, logonType, VARIANT{} /* sddl */,
|
||||
getter_AddRefs(unusedTaskOutput));
|
||||
|
||||
if (aResult) {
|
||||
*aResult = hr;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsWinTaskSchedulerService::DeleteTask(const char16_t* aFolderName,
|
||||
const char16_t* aTaskName) {
|
||||
if (!aFolderName || !aTaskName) {
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
auto folder = GetTaskFolder(aFolderName);
|
||||
if (!folder.isOk()) {
|
||||
return ToNotFoundOrFailure(folder.unwrapErr());
|
||||
}
|
||||
|
||||
BStrPtr bstrTaskName =
|
||||
BStrPtr(::SysAllocString(reinterpret_cast<const OLECHAR*>(aTaskName)));
|
||||
|
||||
HRESULT hr = folder.unwrap()->DeleteTask(bstrTaskName.get(), 0 /* flags */);
|
||||
if (FAILED(hr)) {
|
||||
return ToNotFoundOrFailure(hr);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsWinTaskSchedulerService::GetFolderTasks(const char16_t* aFolderName,
|
||||
nsTArray<nsString>& aResult) {
|
||||
if (!aFolderName) {
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
auto folder = GetTaskFolder(aFolderName);
|
||||
if (!folder.isOk()) {
|
||||
return ToNotFoundOrFailure(folder.unwrapErr());
|
||||
}
|
||||
|
||||
RefPtr<IRegisteredTaskCollection> taskCollection = nullptr;
|
||||
if (FAILED(folder.unwrap()->GetTasks(TASK_ENUM_HIDDEN,
|
||||
getter_AddRefs(taskCollection)))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
LONG taskCount = 0;
|
||||
if (FAILED(taskCollection->get_Count(&taskCount))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
aResult.Clear();
|
||||
|
||||
for (LONG i = 0; i < taskCount; ++i) {
|
||||
RefPtr<IRegisteredTask> task = nullptr;
|
||||
|
||||
// nb: Collections are indexed from 1.
|
||||
if (FAILED(taskCollection->get_Item(_variant_t(i + 1),
|
||||
getter_AddRefs(task)))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
BStrPtr bstrTaskName;
|
||||
{
|
||||
BSTR tempTaskName = nullptr;
|
||||
if (FAILED(task->get_Name(&tempTaskName))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
bstrTaskName = BStrPtr(tempTaskName);
|
||||
}
|
||||
|
||||
aResult.AppendElement(nsString(bstrTaskName.get()));
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsWinTaskSchedulerService::CreateFolder(const char16_t* aParentFolderName,
|
||||
const char16_t* aSubFolderName) {
|
||||
if (!aParentFolderName || !aSubFolderName) {
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
auto parentFolder = GetTaskFolder(aParentFolderName);
|
||||
if (!parentFolder.isOk()) {
|
||||
return ToNotFoundOrFailure(parentFolder.unwrapErr());
|
||||
}
|
||||
|
||||
BStrPtr bstrSubFolderName = BStrPtr(
|
||||
::SysAllocString(reinterpret_cast<const OLECHAR*>(aSubFolderName)));
|
||||
|
||||
HRESULT hr = parentFolder.unwrap()->CreateFolder(bstrSubFolderName.get(),
|
||||
VARIANT{}, // sddl
|
||||
nullptr); // ppFolder
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return ToAlreadyExistsOrFailure(hr);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsWinTaskSchedulerService::DeleteFolder(const char16_t* aParentFolderName,
|
||||
const char16_t* aSubFolderName) {
|
||||
if (!aParentFolderName || !aSubFolderName) {
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
auto parentFolder = GetTaskFolder(aParentFolderName);
|
||||
if (!parentFolder.isOk()) {
|
||||
return ToNotFoundOrFailure(parentFolder.unwrapErr());
|
||||
}
|
||||
|
||||
BStrPtr bstrSubFolderName = BStrPtr(
|
||||
::SysAllocString(reinterpret_cast<const OLECHAR*>(aSubFolderName)));
|
||||
|
||||
HRESULT hr = parentFolder.unwrap()->DeleteFolder(bstrSubFolderName.get(),
|
||||
0 /* flags */);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
if (hr == HRESULT_FROM_WIN32(ERROR_DIR_NOT_EMPTY)) {
|
||||
return NS_ERROR_FILE_DIR_NOT_EMPTY;
|
||||
} else {
|
||||
return ToNotFoundOrFailure(hr);
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
22
toolkit/components/taskscheduler/nsWinTaskScheduler.h
Normal file
22
toolkit/components/taskscheduler/nsWinTaskScheduler.h
Normal file
@ -0,0 +1,22 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef WINTASKSCHEDULER_H_
|
||||
#define WINTASKSCHEDULER_H_
|
||||
|
||||
#include "nsIWinTaskSchedulerService.h"
|
||||
|
||||
class nsWinTaskSchedulerService : public nsIWinTaskSchedulerService {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIWINTASKSCHEDULERSERVICE
|
||||
|
||||
nsWinTaskSchedulerService() = default;
|
||||
|
||||
protected:
|
||||
virtual ~nsWinTaskSchedulerService() = default;
|
||||
};
|
||||
|
||||
#endif /* WINTASKSCHEDULER_H_ */
|
@ -0,0 +1,191 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
// Unit tests for access to the Windows Task Scheduler via nsIWinTaskSchedulerService.
|
||||
|
||||
const svc = Cc["@mozilla.org/win-task-scheduler-service;1"].getService(
|
||||
Ci.nsIWinTaskSchedulerService
|
||||
);
|
||||
|
||||
const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(
|
||||
Ci.nsIUUIDGenerator
|
||||
);
|
||||
|
||||
function randomName() {
|
||||
return (
|
||||
"moz-taskschd-test-" +
|
||||
uuidGenerator
|
||||
.generateUUID()
|
||||
.toString()
|
||||
.slice(1, -1)
|
||||
);
|
||||
}
|
||||
|
||||
const gParentFolderName = randomName();
|
||||
const gParentFolderPath = `\\${gParentFolderName}`;
|
||||
const gSubFolderName = randomName();
|
||||
const gSubFolderPath = `\\${gParentFolderName}\\${gSubFolderName}`;
|
||||
// This folder will not be created
|
||||
const gMissingFolderName = randomName();
|
||||
const gMissingFolderPath = `\\${gParentFolderName}\\${gMissingFolderName}`;
|
||||
|
||||
const gValidTaskXML = `<Task xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
||||
<Triggers />
|
||||
<Settings>
|
||||
<Enabled>false</Enabled>
|
||||
</Settings>
|
||||
<Actions>
|
||||
<Exec>
|
||||
<Command>xyz123.exe</Command>
|
||||
</Exec>
|
||||
</Actions>
|
||||
</Task>`;
|
||||
|
||||
// Missing actions
|
||||
const gInvalidTaskXML = `<Task xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
||||
<Triggers />
|
||||
<Settings>
|
||||
<Enabled>false</Enabled>
|
||||
</Settings>
|
||||
</Task>`;
|
||||
|
||||
function cleanup() {
|
||||
let tasksToDelete = svc.getFolderTasks(gSubFolderPath);
|
||||
|
||||
for (const task of tasksToDelete) {
|
||||
svc.deleteTask(gSubFolderPath, task);
|
||||
}
|
||||
|
||||
svc.deleteFolder(gParentFolderPath, gSubFolderName);
|
||||
|
||||
svc.deleteFolder("\\", gParentFolderPath);
|
||||
}
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
try {
|
||||
cleanup();
|
||||
} catch (_ex) {
|
||||
// Folders may not exist
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_svc() {
|
||||
/***** FOLDERS *****/
|
||||
|
||||
// Try creating subfolder before parent folder exists
|
||||
Assert.throws(
|
||||
() => svc.createFolder(gParentFolderPath, gSubFolderName),
|
||||
/NS_ERROR_FILE_NOT_FOUND/
|
||||
);
|
||||
|
||||
// Create parent folder
|
||||
svc.createFolder("\\", gParentFolderName);
|
||||
|
||||
// Create subfolder
|
||||
svc.createFolder(gParentFolderPath, gSubFolderName);
|
||||
|
||||
// Try creating existing folder
|
||||
Assert.throws(
|
||||
() => svc.createFolder(gParentFolderPath, gSubFolderName),
|
||||
/NS_ERROR_FILE_ALREADY_EXISTS/
|
||||
);
|
||||
|
||||
// Try deleting nonexistent subfolder
|
||||
Assert.throws(
|
||||
() => svc.deleteFolder(gParentFolderPath, gMissingFolderName),
|
||||
/NS_ERROR_FILE_NOT_FOUND/
|
||||
);
|
||||
|
||||
/***** TASKS *****/
|
||||
const taskNames = [randomName(), randomName(), randomName()];
|
||||
|
||||
// Try enumerating nonexistent subfolder
|
||||
Assert.throws(
|
||||
() => svc.getFolderTasks(gMissingFolderPath),
|
||||
/NS_ERROR_FILE_NOT_FOUND/
|
||||
);
|
||||
|
||||
// List empty subfolder
|
||||
Assert.deepEqual(svc.getFolderTasks(gSubFolderPath), []);
|
||||
|
||||
// Try to create task in nonexistent subfolder
|
||||
Assert.throws(
|
||||
() => svc.registerTask(gMissingFolderPath, taskNames[0], gValidTaskXML),
|
||||
/NS_ERROR_FILE_NOT_FOUND/
|
||||
);
|
||||
|
||||
// Create task 0
|
||||
|
||||
svc.registerTask(gSubFolderPath, taskNames[0], gValidTaskXML);
|
||||
|
||||
// Try to recreate task 0
|
||||
Assert.throws(
|
||||
() => svc.registerTask(gSubFolderPath, taskNames[0], gValidTaskXML),
|
||||
/NS_ERROR_FILE_ALREADY_EXISTS/
|
||||
);
|
||||
|
||||
// Update task 0
|
||||
svc.registerTask(
|
||||
gSubFolderPath,
|
||||
taskNames[0],
|
||||
gValidTaskXML,
|
||||
true /* aUpdateExisting */
|
||||
);
|
||||
|
||||
// Read back XML
|
||||
Assert.ok(svc.getTaskXML(gSubFolderPath, taskNames[0]));
|
||||
|
||||
// Create remaining tasks
|
||||
for (const task of taskNames.slice(1)) {
|
||||
svc.registerTask(gSubFolderPath, task, gValidTaskXML);
|
||||
}
|
||||
|
||||
// Try to create with invalid XML
|
||||
Assert.throws(
|
||||
() => svc.registerTask(gSubFolderPath, randomName(), gInvalidTaskXML),
|
||||
/NS_ERROR_FAILURE/
|
||||
);
|
||||
|
||||
// Validate XML
|
||||
Assert.equal(svc.validateTaskDefinition(gValidTaskXML), 0 /* S_OK */);
|
||||
|
||||
// Try to validate invalid XML
|
||||
Assert.notEqual(svc.validateTaskDefinition(gInvalidTaskXML), 0 /* S_OK */);
|
||||
|
||||
// Test enumeration
|
||||
{
|
||||
let foundTasks = svc.getFolderTasks(gSubFolderPath);
|
||||
foundTasks.sort();
|
||||
|
||||
let allTasks = taskNames.slice();
|
||||
allTasks.sort();
|
||||
|
||||
Assert.deepEqual(foundTasks, allTasks);
|
||||
}
|
||||
|
||||
// Try deleting non-empty folder
|
||||
Assert.throws(
|
||||
() => svc.deleteFolder(gParentFolderPath, gSubFolderName),
|
||||
/NS_ERROR_FILE_DIR_NOT_EMPTY/
|
||||
);
|
||||
|
||||
const missingTaskName = randomName();
|
||||
|
||||
// Try deleting non-existent task
|
||||
Assert.throws(
|
||||
() => svc.deleteTask(gSubFolderName, missingTaskName),
|
||||
/NS_ERROR_FILE_NOT_FOUND/
|
||||
);
|
||||
|
||||
// Try reading non-existent task
|
||||
Assert.throws(
|
||||
() => svc.getTaskXML(gSubFolderPath, missingTaskName),
|
||||
/NS_ERROR_FILE_NOT_FOUND/
|
||||
);
|
||||
|
||||
/***** Cleanup *****/
|
||||
// Explicitly call cleanup() to test that it removes the folder without error.
|
||||
cleanup();
|
||||
});
|
@ -0,0 +1,2 @@
|
||||
[test_WinTaskSchedulerService.js]
|
||||
run-if = os == "win" # Test of Windows only service
|
Loading…
x
Reference in New Issue
Block a user