2 * Task Scheduler Service
4 * Copyright 2014 Dmitry Timoshkov
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
31 #include "wine/debug.h"
33 #include "schedsvc_private.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(schedsvc
);
37 static const char bom_utf8
[] = { 0xef,0xbb,0xbf };
44 HRESULT __cdecl
SchRpcHighestVersion(DWORD
*version
)
46 TRACE("%p\n", version
);
48 *version
= MAKELONG(3, 1);
52 static WCHAR
*get_full_name(const WCHAR
*path
, WCHAR
**relative_path
)
54 static const WCHAR tasksW
[] = { '\\','t','a','s','k','s','\\',0 };
58 len
= GetSystemDirectoryW(NULL
, 0);
59 len
+= lstrlenW(tasksW
) + lstrlenW(path
);
61 target
= malloc(len
* sizeof(WCHAR
));
64 GetSystemDirectoryW(target
, len
);
65 lstrcatW(target
, tasksW
);
67 *relative_path
= target
+ lstrlenW(target
) - 1;
68 while (*path
== '\\') path
++;
69 lstrcatW(target
, path
);
75 * Recursively create all directories in the path.
77 static HRESULT
create_directory(const WCHAR
*path
)
83 new_path
= malloc((lstrlenW(path
) + 1) * sizeof(WCHAR
));
84 if (!new_path
) return E_OUTOFMEMORY
;
86 lstrcpyW(new_path
, path
);
88 len
= lstrlenW(new_path
);
89 while (len
&& new_path
[len
- 1] == '\\')
91 new_path
[len
- 1] = 0;
95 while (!CreateDirectoryW(new_path
, NULL
))
98 DWORD last_error
= GetLastError();
100 if (last_error
!= ERROR_PATH_NOT_FOUND
|| !(slash
= wcsrchr(new_path
, '\\')))
102 hr
= HRESULT_FROM_WIN32(last_error
);
106 len
= slash
- new_path
;
108 hr
= create_directory(new_path
);
109 if (hr
!= S_OK
) break;
110 new_path
[len
] = '\\';
117 static HRESULT
write_xml_utf8(const WCHAR
*name
, DWORD disposition
, const WCHAR
*xmlW
)
119 static const char comment
[] = "<!-- Task definition created by Wine -->\n";
125 hfile
= CreateFileW(name
, GENERIC_WRITE
, 0, NULL
, disposition
, 0, 0);
126 if (hfile
== INVALID_HANDLE_VALUE
)
128 if (GetLastError() == ERROR_FILE_EXISTS
)
129 return HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS
);
131 return HRESULT_FROM_WIN32(GetLastError());
134 size
= WideCharToMultiByte(CP_UTF8
, 0, xmlW
, -1, NULL
, 0, NULL
, NULL
);
139 return E_OUTOFMEMORY
;
141 WideCharToMultiByte(CP_UTF8
, 0, xmlW
, -1, xml
, size
, NULL
, NULL
);
143 if (!WriteFile(hfile
, bom_utf8
, sizeof(bom_utf8
), &size
, NULL
))
145 hr
= HRESULT_FROM_WIN32(GetLastError());
148 if (!WriteFile(hfile
, comment
, strlen(comment
), &size
, NULL
))
150 hr
= HRESULT_FROM_WIN32(GetLastError());
154 /* skip XML declaration with UTF-16 specifier */
155 if (!memcmp(xml
, "<?xml", 5))
157 const char *p
= strchr(xml
, '>');
158 if (p
++) while (isspace(*p
)) p
++;
160 if (!WriteFile(hfile
, p
, strlen(p
), &size
, NULL
))
161 hr
= HRESULT_FROM_WIN32(GetLastError());
165 if (!WriteFile(hfile
, xml
, strlen(xml
), &size
, NULL
))
166 hr
= HRESULT_FROM_WIN32(GetLastError());
175 HRESULT __cdecl
SchRpcRegisterTask(const WCHAR
*path
, const WCHAR
*xml
, DWORD flags
, const WCHAR
*sddl
,
176 DWORD task_logon_type
, DWORD n_creds
, const TASK_USER_CRED
*creds
,
177 WCHAR
**actual_path
, TASK_XML_ERROR_INFO
**xml_error_info
)
179 WCHAR
*full_name
, *relative_path
;
183 TRACE("%s,%s,%#lx,%s,%lu,%lu,%p,%p,%p\n", debugstr_w(path
), debugstr_w(xml
), flags
,
184 debugstr_w(sddl
), task_logon_type
, n_creds
, creds
, actual_path
, xml_error_info
);
187 *xml_error_info
= NULL
;
189 /* FIXME: assume that validation is performed on the client side */
190 if (flags
& TASK_VALIDATE_ONLY
) return S_OK
;
194 full_name
= get_full_name(path
, &relative_path
);
195 if (!full_name
) return E_OUTOFMEMORY
;
197 if (wcschr(path
, '\\') || wcschr(path
, '/'))
199 WCHAR
*p
= wcsrchr(full_name
, '/');
200 if (!p
) p
= wcsrchr(full_name
, '\\');
202 hr
= create_directory(full_name
);
203 if (hr
!= S_OK
&& hr
!= HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS
))
217 StringFromGUID2(&iid
, uuid_str
, 39);
219 full_name
= get_full_name(uuid_str
, &relative_path
);
220 if (!full_name
) return E_OUTOFMEMORY
;
221 /* skip leading '\' */
225 switch (flags
& (TASK_CREATE
| TASK_UPDATE
))
229 disposition
= CREATE_NEW
;
233 disposition
= OPEN_EXISTING
;
236 case (TASK_CREATE
| TASK_UPDATE
):
237 disposition
= OPEN_ALWAYS
;
241 hr
= write_xml_utf8(full_name
, disposition
, xml
);
244 *actual_path
= wcsdup(relative_path
);
245 schedsvc_auto_start();
252 static int detect_encoding(const void *buffer
, DWORD size
)
254 if (size
>= sizeof(bom_utf8
) && !memcmp(buffer
, bom_utf8
, sizeof(bom_utf8
)))
258 int flags
= IS_TEXT_UNICODE_SIGNATURE
|
259 IS_TEXT_UNICODE_REVERSE_SIGNATURE
|
260 IS_TEXT_UNICODE_ODD_LENGTH
;
261 IsTextUnicode(buffer
, size
, &flags
);
262 if (flags
& IS_TEXT_UNICODE_SIGNATURE
)
264 if (flags
& IS_TEXT_UNICODE_REVERSE_SIGNATURE
)
270 static HRESULT
read_xml(const WCHAR
*name
, WCHAR
**xml
)
278 attrs
= GetFileAttributesW(name
);
279 if (attrs
== INVALID_FILE_ATTRIBUTES
)
280 return HRESULT_FROM_WIN32(GetLastError());
281 if (attrs
& FILE_ATTRIBUTE_DIRECTORY
)
282 return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND
);
284 hfile
= CreateFileW(name
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
, 0, 0);
285 if (hfile
== INVALID_HANDLE_VALUE
)
286 return HRESULT_FROM_WIN32(GetLastError());
288 size
= GetFileSize(hfile
, NULL
);
289 buff
= src
= malloc(size
+ 2);
293 return E_OUTOFMEMORY
;
299 ReadFile(hfile
, src
, size
, &size
, NULL
);
302 cp
= detect_encoding(src
, size
);
309 if (cp
== CP_UTF8
&& size
>= sizeof(bom_utf8
) && !memcmp(src
, bom_utf8
, sizeof(bom_utf8
)))
310 src
+= sizeof(bom_utf8
);
312 size
= MultiByteToWideChar(cp
, 0, src
, -1, NULL
, 0);
313 *xml
= malloc(size
* sizeof(WCHAR
));
315 MultiByteToWideChar(cp
, 0, src
, -1, *xml
, size
);
323 static HRESULT
read_text_value(IXmlReader
*reader
, WCHAR
**value
)
328 while (IXmlReader_Read(reader
, &type
) == S_OK
)
332 case XmlNodeType_Text
:
333 if (FAILED(hr
= IXmlReader_GetValue(reader
, (const WCHAR
**)value
, NULL
)))
335 TRACE("%s\n", debugstr_w(*value
));
338 case XmlNodeType_Whitespace
:
339 case XmlNodeType_Comment
:
343 FIXME("unexpected node type %d\n", type
);
351 static HRESULT
read_variantbool_value(IXmlReader
*reader
, VARIANT_BOOL
*vbool
)
356 *vbool
= VARIANT_FALSE
;
358 if (FAILED(hr
= read_text_value(reader
, &value
)))
361 if (!wcscmp(value
, L
"true"))
363 *vbool
= VARIANT_TRUE
;
365 else if (wcscmp(value
, L
"false"))
367 WARN("unexpected bool value %s\n", debugstr_w(value
));
368 return SCHED_E_INVALIDVALUE
;
374 static HRESULT
read_task_settings(IXmlReader
*reader
, struct task_info
*info
)
376 VARIANT_BOOL bool_val
;
381 if (IXmlReader_IsEmptyElement(reader
))
383 TRACE("Settings is empty.\n");
387 while (IXmlReader_Read(reader
, &type
) == S_OK
)
391 case XmlNodeType_EndElement
:
392 hr
= IXmlReader_GetLocalName(reader
, &name
, NULL
);
393 if (hr
!= S_OK
) return hr
;
395 TRACE("/%s\n", debugstr_w(name
));
397 if (!wcscmp(name
, L
"Settings"))
402 case XmlNodeType_Element
:
403 hr
= IXmlReader_GetLocalName(reader
, &name
, NULL
);
404 if (hr
!= S_OK
) return hr
;
406 TRACE("Element: %s\n", debugstr_w(name
));
408 if (!wcscmp(name
, L
"Enabled"))
410 if (FAILED(hr
= read_variantbool_value(reader
, &bool_val
)))
412 info
->enabled
= !!bool_val
;
420 WARN("Settings was not terminated\n");
421 return SCHED_E_MALFORMEDXML
;
424 static HRESULT
read_task_info(IXmlReader
*reader
, struct task_info
*info
)
430 if (IXmlReader_IsEmptyElement(reader
))
432 TRACE("Task is empty\n");
435 while (IXmlReader_Read(reader
, &type
) == S_OK
)
439 case XmlNodeType_EndElement
:
440 if (FAILED(hr
= IXmlReader_GetLocalName(reader
, &name
, NULL
)))
443 if (!wcscmp(name
, L
"Task"))
447 case XmlNodeType_Element
:
448 if (FAILED(hr
= IXmlReader_GetLocalName(reader
, &name
, NULL
)))
451 TRACE("Element: %s\n", debugstr_w(name
));
453 if (!wcscmp(name
, L
"Settings"))
455 if (FAILED(hr
= read_task_settings(reader
, info
)))
465 WARN("Task was not terminated\n");
466 return SCHED_E_MALFORMEDXML
;
470 static HRESULT
read_task_info_from_xml(const WCHAR
*xml
, struct task_info
*info
)
480 memset(info
, 0, sizeof(*info
));
482 if (!(hmem
= GlobalAlloc(0, wcslen(xml
) * sizeof(WCHAR
))))
483 return E_OUTOFMEMORY
;
485 buf
= GlobalLock(hmem
);
486 memcpy(buf
, xml
, lstrlenW(xml
) * sizeof(WCHAR
));
489 if (FAILED(hr
= CreateStreamOnHGlobal(hmem
, TRUE
, &stream
)))
495 if (FAILED(hr
= CreateXmlReader(&IID_IXmlReader
, (void **)&reader
, NULL
)))
497 IStream_Release(stream
);
501 if (FAILED(hr
= IXmlReader_SetInput(reader
, (IUnknown
*)stream
)))
504 while (IXmlReader_Read(reader
, &type
) == S_OK
)
506 if (type
!= XmlNodeType_Element
) continue;
507 if (FAILED(hr
= IXmlReader_GetLocalName(reader
, &name
, NULL
)))
510 TRACE("Element: %s\n", debugstr_w(name
));
511 if (wcscmp(name
, L
"Task"))
514 hr
= read_task_info(reader
, info
);
519 IXmlReader_Release(reader
);
520 IStream_Release(stream
);
523 WARN("Failed parsing xml, hr %#lx.\n", hr
);
524 return SCHED_E_MALFORMEDXML
;
529 HRESULT __cdecl
SchRpcRetrieveTask(const WCHAR
*path
, const WCHAR
*languages
, ULONG
*n_languages
, WCHAR
**xml
)
534 TRACE("%s,%s,%p,%p\n", debugstr_w(path
), debugstr_w(languages
), n_languages
, xml
);
536 full_name
= get_full_name(path
, NULL
);
537 if (!full_name
) return E_OUTOFMEMORY
;
539 hr
= read_xml(full_name
, xml
);
540 if (hr
!= S_OK
) *xml
= NULL
;
546 HRESULT __cdecl
SchRpcCreateFolder(const WCHAR
*path
, const WCHAR
*sddl
, DWORD flags
)
551 TRACE("%s,%s,%#lx\n", debugstr_w(path
), debugstr_w(sddl
), flags
);
553 if (flags
) return E_INVALIDARG
;
555 full_name
= get_full_name(path
, NULL
);
556 if (!full_name
) return E_OUTOFMEMORY
;
558 hr
= create_directory(full_name
);
564 HRESULT __cdecl
SchRpcSetSecurity(const WCHAR
*path
, const WCHAR
*sddl
, DWORD flags
)
566 FIXME("%s,%s,%#lx: stub\n", debugstr_w(path
), debugstr_w(sddl
), flags
);
570 HRESULT __cdecl
SchRpcGetSecurity(const WCHAR
*path
, DWORD flags
, WCHAR
**sddl
)
572 FIXME("%s,%#lx,%p: stub\n", debugstr_w(path
), flags
, sddl
);
576 static void free_list(TASK_NAMES list
, LONG count
)
580 for (i
= 0; i
< count
; i
++)
586 static inline BOOL
is_directory(const WIN32_FIND_DATAW
*data
)
588 if (data
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
590 if (data
->cFileName
[0] == '.')
592 if (!data
->cFileName
[1] || (data
->cFileName
[1] == '.' && !data
->cFileName
[2]))
600 static inline BOOL
is_file(const WIN32_FIND_DATAW
*data
)
602 return !(data
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
);
605 HRESULT __cdecl
SchRpcEnumFolders(const WCHAR
*path
, DWORD flags
, DWORD
*start_index
, DWORD n_requested
,
606 DWORD
*n_names
, TASK_NAMES
*names
)
608 static const WCHAR allW
[] = {'\\','*',0};
611 WCHAR pathW
[MAX_PATH
];
612 WIN32_FIND_DATAW data
;
614 DWORD allocated
, count
, index
;
617 TRACE("%s,%#lx,%lu,%lu,%p,%p\n", debugstr_w(path
), flags
, *start_index
, n_requested
, n_names
, names
);
622 if (flags
& ~TASK_ENUM_HIDDEN
) return E_INVALIDARG
;
624 if (!n_requested
) n_requested
= ~0u;
626 full_name
= get_full_name(path
, NULL
);
627 if (!full_name
) return E_OUTOFMEMORY
;
629 if (lstrlenW(full_name
) + 2 > MAX_PATH
)
632 return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE
);
635 lstrcpyW(pathW
, full_name
);
636 lstrcatW(pathW
, allW
);
641 list
= malloc(allocated
* sizeof(list
[0]));
642 if (!list
) return E_OUTOFMEMORY
;
646 handle
= FindFirstFileW(pathW
, &data
);
647 if (handle
== INVALID_HANDLE_VALUE
)
650 if (GetLastError() == ERROR_PATH_NOT_FOUND
)
651 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND
);
652 return HRESULT_FROM_WIN32(GetLastError());
657 if (is_directory(&data
) && index
++ >= *start_index
)
659 if (count
>= allocated
)
663 new_list
= heap_realloc(list
, allocated
* sizeof(list
[0]));
672 TRACE("adding %s\n", debugstr_w(data
.cFileName
));
674 list
[count
] = wcsdup(data
.cFileName
);
683 if (count
>= n_requested
)
689 } while (FindNextFileW(handle
, &data
));
695 free_list(list
, count
);
704 *start_index
= index
;
710 return *start_index
? S_FALSE
: S_OK
;
713 HRESULT __cdecl
SchRpcEnumTasks(const WCHAR
*path
, DWORD flags
, DWORD
*start_index
, DWORD n_requested
,
714 DWORD
*n_names
, TASK_NAMES
*names
)
716 static const WCHAR allW
[] = {'\\','*',0};
719 WCHAR pathW
[MAX_PATH
];
720 WIN32_FIND_DATAW data
;
722 DWORD allocated
, count
, index
;
725 TRACE("%s,%#lx,%lu,%lu,%p,%p\n", debugstr_w(path
), flags
, *start_index
, n_requested
, n_names
, names
);
730 if (flags
& ~TASK_ENUM_HIDDEN
) return E_INVALIDARG
;
732 if (!n_requested
) n_requested
= ~0u;
734 full_name
= get_full_name(path
, NULL
);
735 if (!full_name
) return E_OUTOFMEMORY
;
737 if (lstrlenW(full_name
) + 2 > MAX_PATH
)
740 return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE
);
743 lstrcpyW(pathW
, full_name
);
744 lstrcatW(pathW
, allW
);
749 list
= malloc(allocated
* sizeof(list
[0]));
750 if (!list
) return E_OUTOFMEMORY
;
754 handle
= FindFirstFileW(pathW
, &data
);
755 if (handle
== INVALID_HANDLE_VALUE
)
758 if (GetLastError() == ERROR_PATH_NOT_FOUND
)
759 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND
);
760 return HRESULT_FROM_WIN32(GetLastError());
765 if (is_file(&data
) && index
++ >= *start_index
)
767 if (count
>= allocated
)
771 new_list
= heap_realloc(list
, allocated
* sizeof(list
[0]));
780 TRACE("adding %s\n", debugstr_w(data
.cFileName
));
782 list
[count
] = wcsdup(data
.cFileName
);
791 if (count
>= n_requested
)
797 } while (FindNextFileW(handle
, &data
));
803 free_list(list
, count
);
812 *start_index
= index
;
818 return *start_index
? S_FALSE
: S_OK
;
821 HRESULT __cdecl
SchRpcEnumInstances(const WCHAR
*path
, DWORD flags
, DWORD
*n_guids
, GUID
**guids
)
823 FIXME("%s,%#lx,%p,%p: stub\n", debugstr_w(path
), flags
, n_guids
, guids
);
827 HRESULT __cdecl
SchRpcGetInstanceInfo(GUID guid
, WCHAR
**path
, DWORD
*task_state
, WCHAR
**action
,
828 WCHAR
**info
, DWORD
*n_instances
, GUID
**instances
, DWORD
*pid
)
830 FIXME("%s,%p,%p,%p,%p,%p,%p,%p: stub\n", wine_dbgstr_guid(&guid
), path
, task_state
, action
,
831 info
, n_instances
, instances
, pid
);
835 HRESULT __cdecl
SchRpcStopInstance(GUID guid
, DWORD flags
)
837 FIXME("%s,%#lx: stub\n", wine_dbgstr_guid(&guid
), flags
);
841 HRESULT __cdecl
SchRpcStop(const WCHAR
*path
, DWORD flags
)
843 FIXME("%s,%#lx: stub\n", debugstr_w(path
), flags
);
847 HRESULT __cdecl
SchRpcRun(const WCHAR
*path
, DWORD n_args
, const WCHAR
**args
, DWORD flags
,
848 DWORD session_id
, const WCHAR
*user
, GUID
*guid
)
850 FIXME("%s,%lu,%p,%#lx,%#lx,%s,%p: stub\n", debugstr_w(path
), n_args
, args
, flags
,
851 session_id
, debugstr_w(user
), guid
);
855 HRESULT __cdecl
SchRpcDelete(const WCHAR
*path
, DWORD flags
)
860 TRACE("%s,%#lx\n", debugstr_w(path
), flags
);
862 if (flags
) return E_INVALIDARG
;
864 while (*path
== '\\' || *path
== '/') path
++;
865 if (!*path
) return E_ACCESSDENIED
;
867 full_name
= get_full_name(path
, NULL
);
868 if (!full_name
) return E_OUTOFMEMORY
;
870 if (!RemoveDirectoryW(full_name
))
872 hr
= HRESULT_FROM_WIN32(GetLastError());
873 if (hr
== HRESULT_FROM_WIN32(ERROR_DIRECTORY
))
874 hr
= DeleteFileW(full_name
) ? S_OK
: HRESULT_FROM_WIN32(GetLastError());
881 HRESULT __cdecl
SchRpcRename(const WCHAR
*path
, const WCHAR
*name
, DWORD flags
)
883 FIXME("%s,%s,%#lx: stub\n", debugstr_w(path
), debugstr_w(name
), flags
);
887 HRESULT __cdecl
SchRpcScheduledRuntimes(const WCHAR
*path
, SYSTEMTIME
*start
, SYSTEMTIME
*end
, DWORD flags
,
888 DWORD n_requested
, DWORD
*n_runtimes
, SYSTEMTIME
**runtimes
)
890 FIXME("%s,%p,%p,%#lx,%lu,%p,%p: stub\n", debugstr_w(path
), start
, end
, flags
,
891 n_requested
, n_runtimes
, runtimes
);
895 HRESULT __cdecl
SchRpcGetLastRunInfo(const WCHAR
*path
, SYSTEMTIME
*last_runtime
, DWORD
*last_return_code
)
897 FIXME("%s,%p,%p: stub\n", debugstr_w(path
), last_runtime
, last_return_code
);
901 HRESULT __cdecl
SchRpcGetTaskInfo(const WCHAR
*path
, DWORD flags
, DWORD
*enabled
, DWORD
*task_state
)
903 WCHAR
*full_name
, *xml
;
904 struct task_info info
;
907 FIXME("%s,%#lx,%p,%p: stub\n", debugstr_w(path
), flags
, enabled
, task_state
);
909 full_name
= get_full_name(path
, NULL
);
910 if (!full_name
) return E_OUTOFMEMORY
;
912 hr
= read_xml(full_name
, &xml
);
914 if (hr
!= S_OK
) return hr
;
915 hr
= read_task_info_from_xml(xml
, &info
);
917 if (FAILED(hr
)) return hr
;
919 *enabled
= info
.enabled
;
920 if (flags
& SCH_FLAG_STATE
)
921 *task_state
= *enabled
? TASK_STATE_READY
: TASK_STATE_DISABLED
;
923 *task_state
= TASK_STATE_UNKNOWN
;
927 HRESULT __cdecl
SchRpcGetNumberOfMissedRuns(const WCHAR
*path
, DWORD
*runs
)
929 FIXME("%s,%p: stub\n", debugstr_w(path
), runs
);
933 HRESULT __cdecl
SchRpcEnableTask(const WCHAR
*path
, DWORD enabled
)
935 FIXME("%s,%lu: stub\n", debugstr_w(path
), enabled
);