freemyid 0.2.0.dev1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- freemyid/__init__.py +2 -0
- freemyid/_data/brokers/LICENSE-DATA.md +28 -0
- freemyid/_data/brokers/registry.sample.yaml +674 -0
- freemyid/_data/brokers/state_laws.yaml +448 -0
- freemyid/_data/schema/registry.schema.json +668 -0
- freemyid/_data/schema/setup-config.schema.json +538 -0
- freemyid/_data/schema/state_laws.schema.json +222 -0
- freemyid/cli.py +725 -0
- freemyid/cli_json.py +198 -0
- freemyid/config.py +43 -0
- freemyid/consent.py +82 -0
- freemyid/crypto.py +197 -0
- freemyid/doctor.py +374 -0
- freemyid/engine/__init__.py +0 -0
- freemyid/engine/_dev/__init__.py +101 -0
- freemyid/engine/_dev/devrun.py +101 -0
- freemyid/engine/_dev/inbox/devauto.eml +7 -0
- freemyid/engine/_dev/registry.yaml +106 -0
- freemyid/engine/_dev/site/devauto.example/confirm.html +1 -0
- freemyid/engine/_dev/site/devauto.example/name/jane-testperson/il.html +17 -0
- freemyid/engine/_dev/site/devauto.example/name/jane-testperson/nc.html +4 -0
- freemyid/engine/_dev/site/devauto.example/name/jane-testperson/oh.html +4 -0
- freemyid/engine/_dev/site/devauto.example/optout/done.html +1 -0
- freemyid/engine/_dev/site/devauto.example/optout.html +6 -0
- freemyid/engine/_dev/site/devblocked.example/name/jane-testperson/il.html +3 -0
- freemyid/engine/_dev/site/devblocked.example/optout.html +3 -0
- freemyid/engine/_dev/site/devfill.example/ajax.html +20 -0
- freemyid/engine/_dev/site/devfill.example/aria.html +16 -0
- freemyid/engine/_dev/site/devfill.example/consent.html +20 -0
- freemyid/engine/_dev/site/devfill.example/done.html +3 -0
- freemyid/engine/_dev/site/devfill.example/empty.html +1 -0
- freemyid/engine/_dev/site/devfill.example/fullname.html +12 -0
- freemyid/engine/_dev/site/devfill.example/labels.html +32 -0
- freemyid/engine/_dev/site/devfill.example/late.html +22 -0
- freemyid/engine/_dev/site/devfind.example/hostile.html +12 -0
- freemyid/engine/_dev/site/devfind.example/p/jane-testperson-shelbyville-il.html +6 -0
- freemyid/engine/_dev/site/devfind.example/p/jane-testperson-springfield-il.html +6 -0
- freemyid/engine/_dev/site/devfind.example/results.html +11 -0
- freemyid/engine/_dev/site/devfind.example/search.html +16 -0
- freemyid/engine/_dev/site/devweb.example/remove.html +1 -0
- freemyid/engine/browser.py +430 -0
- freemyid/engine/fill_for_me.py +630 -0
- freemyid/engine/filler.py +523 -0
- freemyid/engine/handoff.py +237 -0
- freemyid/engine/letters.py +125 -0
- freemyid/engine/listing_finder.py +538 -0
- freemyid/engine/mail.py +406 -0
- freemyid/engine/mail_tls.py +198 -0
- freemyid/engine/matcher.py +208 -0
- freemyid/engine/outbox.py +421 -0
- freemyid/engine/plugins.py +68 -0
- freemyid/engine/runner.py +791 -0
- freemyid/engine/tiers.py +107 -0
- freemyid/interfaces.py +164 -0
- freemyid/logging_setup.py +152 -0
- freemyid/models.py +700 -0
- freemyid/netpolicy.py +145 -0
- freemyid/notify.py +58 -0
- freemyid/paths.py +83 -0
- freemyid/registry.py +275 -0
- freemyid/scheduler/__init__.py +203 -0
- freemyid/scheduler/render.py +91 -0
- freemyid/secret_backends.py +562 -0
- freemyid/secrets.py +25 -0
- freemyid/setup_config.py +311 -0
- freemyid/state.py +151 -0
- freemyid/storage/__init__.py +3 -0
- freemyid/storage/db.py +394 -0
- freemyid/submitters/__init__.py +0 -0
- freemyid/submitters/assisted.py +171 -0
- freemyid/submitters/base.py +119 -0
- freemyid/submitters/email.py +40 -0
- freemyid/submitters/web_form.py +276 -0
- freemyid/templates/_identity.txt.j2 +19 -0
- freemyid/templates/_signature.txt.j2 +7 -0
- freemyid/templates/ccpa.txt.j2 +16 -0
- freemyid/templates/follow_up.txt.j2 +15 -0
- freemyid/templates/generic_removal.txt.j2 +12 -0
- freemyid/templates/legal_demand.txt.j2 +20 -0
- freemyid/templates/phone_script.txt.j2 +25 -0
- freemyid/templates/postal_letter.txt.j2 +20 -0
- freemyid/templates/state_privacy.txt.j2 +17 -0
- freemyid/wizard/__init__.py +0 -0
- freemyid/wizard/dashboard.py +401 -0
- freemyid/wizard/desktop.py +188 -0
- freemyid/wizard/draft.py +627 -0
- freemyid/wizard/firstrun.py +42 -0
- freemyid/wizard/flows.py +283 -0
- freemyid/wizard/report.py +86 -0
- freemyid/wizard/server.py +834 -0
- freemyid/wizard/static/__init__.py +0 -0
- freemyid/wizard/static/app.js +98 -0
- freemyid/wizard/static/freemyid.png +0 -0
- freemyid/wizard/static/style.css +85 -0
- freemyid/wizard/ui.py +343 -0
- freemyid-0.2.0.dev1.dist-info/METADATA +396 -0
- freemyid-0.2.0.dev1.dist-info/RECORD +100 -0
- freemyid-0.2.0.dev1.dist-info/WHEEL +4 -0
- freemyid-0.2.0.dev1.dist-info/entry_points.txt +2 -0
- freemyid-0.2.0.dev1.dist-info/licenses/LICENSE +661 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Background re-check job: install / remove / status. Runs `freemyid run --scheduled`.
|
|
2
|
+
|
|
3
|
+
Rendering lives in render.py (pure, testable). The functions here are the only code that
|
|
4
|
+
touches the system, and only when the user explicitly asks (`freemyid schedule install`).
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from .. import paths
|
|
15
|
+
from . import render
|
|
16
|
+
|
|
17
|
+
__all__ = ["install", "job_command", "platform_name", "remove", "status", "windows_create_args",
|
|
18
|
+
"windows_delete_args", "windows_query_args"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def platform_name() -> str:
|
|
22
|
+
if sys.platform.startswith("linux"):
|
|
23
|
+
return "linux"
|
|
24
|
+
if sys.platform == "darwin":
|
|
25
|
+
return "macos"
|
|
26
|
+
if sys.platform == "win32":
|
|
27
|
+
return "windows"
|
|
28
|
+
raise RuntimeError("Scheduling is not supported on this system.")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _windowless_python() -> str:
|
|
32
|
+
"""pythonw.exe next to python.exe (no console window); falls back to python.exe."""
|
|
33
|
+
exe = Path(sys.executable)
|
|
34
|
+
pw = exe.with_name("pythonw.exe")
|
|
35
|
+
return str(pw) if pw.exists() else str(exe)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def job_command(frozen: bool | None = None, platform: str | None = None) -> list[str]:
|
|
39
|
+
"""The command the scheduler runs. Arguments let tests render it for any OS."""
|
|
40
|
+
frozen = getattr(sys, "frozen", False) if frozen is None else frozen
|
|
41
|
+
win = (sys.platform == "win32") if platform is None else platform == "windows"
|
|
42
|
+
# Linux/macOS jobs run `run --headless --scheduled` (no prompts, fails closed).
|
|
43
|
+
# DEPENDENCY: the --headless flag is added to cli.py by another engineer.
|
|
44
|
+
tail = ["run", "--scheduled"] if win else ["run", "--headless", "--scheduled"]
|
|
45
|
+
if frozen:
|
|
46
|
+
exe = Path(sys.executable)
|
|
47
|
+
if win: # the windowed FreeMyID.exe, so no console window flashes
|
|
48
|
+
gui = exe.with_name("FreeMyID.exe")
|
|
49
|
+
return [str(gui if gui.exists() else exe), *tail]
|
|
50
|
+
return [str(exe), *tail]
|
|
51
|
+
if win:
|
|
52
|
+
return [_windowless_python(), "-c", "from freemyid.cli import app; app()",
|
|
53
|
+
*tail]
|
|
54
|
+
exe_path = shutil.which("freemyid")
|
|
55
|
+
if exe_path:
|
|
56
|
+
return [exe_path, *tail]
|
|
57
|
+
return [sys.executable, "-c", "from freemyid.cli import app; app()", *tail]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _run(args: list[str], input: str | None = None) -> subprocess.CompletedProcess:
|
|
61
|
+
flags = getattr(subprocess, "CREATE_NO_WINDOW", 0) if sys.platform == "win32" else 0
|
|
62
|
+
return subprocess.run(args, input=input, check=False, capture_output=True, text=True,
|
|
63
|
+
timeout=60, creationflags=flags)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _job_env() -> dict[str, str]:
|
|
67
|
+
"""Settings the job needs that a systemd/launchd session would not inherit."""
|
|
68
|
+
return {k: os.environ[k] for k in render.ENV_PASSTHROUGH if os.environ.get(k)}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _uid() -> int:
|
|
72
|
+
return os.getuid()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _launchd_domain() -> str:
|
|
76
|
+
return f"gui/{_uid()}"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _systemd_dir() -> Path:
|
|
80
|
+
return Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) / "systemd" / "user"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _plist_path() -> Path:
|
|
84
|
+
return Path.home() / "Library" / "LaunchAgents" / f"{render.LABEL}.plist"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _has_systemd_user() -> bool:
|
|
88
|
+
if not shutil.which("systemctl"):
|
|
89
|
+
return False
|
|
90
|
+
try:
|
|
91
|
+
return _run(["systemctl", "--user", "show-environment"]).returncode == 0
|
|
92
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _crontab_get() -> str:
|
|
97
|
+
r = _run(["crontab", "-l"])
|
|
98
|
+
return r.stdout if r.returncode == 0 else ""
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _strip_cron(text: str) -> str:
|
|
102
|
+
return "".join(ln + "\n" for ln in text.splitlines() if render.CRON_MARK not in ln)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def windows_create_args(xml_path: str) -> list[str]:
|
|
106
|
+
# No /RU or /RP: the task belongs to the current user and needs no admin rights.
|
|
107
|
+
return ["schtasks", "/Create", "/TN", render.TASK, "/XML", xml_path, "/F"]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def windows_delete_args() -> list[str]:
|
|
111
|
+
return ["schtasks", "/Delete", "/TN", render.TASK, "/F"]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def windows_query_args() -> list[str]:
|
|
115
|
+
return ["schtasks", "/Query", "/TN", render.TASK]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def install() -> str:
|
|
119
|
+
cmd = job_command()
|
|
120
|
+
plat = platform_name()
|
|
121
|
+
if plat == "linux":
|
|
122
|
+
if _has_systemd_user():
|
|
123
|
+
d = _systemd_dir()
|
|
124
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
125
|
+
(d / f"{render.UNIT}.service").write_text(render.systemd_service(cmd, _job_env()))
|
|
126
|
+
(d / f"{render.UNIT}.timer").write_text(render.systemd_timer())
|
|
127
|
+
_run(["systemctl", "--user", "daemon-reload"])
|
|
128
|
+
r = _run(["systemctl", "--user", "enable", "--now", f"{render.UNIT}.timer"])
|
|
129
|
+
if r.returncode != 0:
|
|
130
|
+
raise RuntimeError("Could not start the systemd timer.")
|
|
131
|
+
return ("Installed a daily systemd user timer. If the computer was off, the check "
|
|
132
|
+
"runs when it next starts. On a server nobody logs in to, also run: "
|
|
133
|
+
"loginctl enable-linger $USER")
|
|
134
|
+
if shutil.which("crontab"):
|
|
135
|
+
new = _strip_cron(_crontab_get()) + render.cron_lines(cmd)
|
|
136
|
+
if _run(["crontab", "-"], input=new).returncode != 0:
|
|
137
|
+
raise RuntimeError("Could not update crontab.")
|
|
138
|
+
return "Installed a daily cron job plus a run at startup."
|
|
139
|
+
raise RuntimeError("Neither systemd nor cron is available.")
|
|
140
|
+
if plat == "macos":
|
|
141
|
+
p = _plist_path()
|
|
142
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
143
|
+
paths.logs_dir().mkdir(parents=True, exist_ok=True)
|
|
144
|
+
p.write_text(render.launchd_plist(cmd, str(paths.logs_dir() / "scheduler.log"),
|
|
145
|
+
_job_env()))
|
|
146
|
+
_run(["launchctl", "bootout", f"{_launchd_domain()}/{render.LABEL}"]) # ok if absent
|
|
147
|
+
if _run(["launchctl", "bootstrap", _launchd_domain(), str(p)]).returncode != 0:
|
|
148
|
+
raise RuntimeError("Could not load the launchd agent.")
|
|
149
|
+
return "Installed a daily launchd agent. Missed runs happen when the Mac wakes."
|
|
150
|
+
xml = paths.home() / "freemyid-task.xml"
|
|
151
|
+
xml.write_text(render.windows_task_xml(cmd), encoding="utf-16")
|
|
152
|
+
try: # /F replaces an existing task, so installing twice is harmless
|
|
153
|
+
r = _run(windows_create_args(str(xml)))
|
|
154
|
+
finally:
|
|
155
|
+
xml.unlink(missing_ok=True)
|
|
156
|
+
if r.returncode != 0:
|
|
157
|
+
raise RuntimeError("Could not create the scheduled task.")
|
|
158
|
+
return "Installed a daily scheduled task that also runs after a missed start."
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def remove() -> str:
|
|
162
|
+
plat = platform_name()
|
|
163
|
+
if plat == "linux":
|
|
164
|
+
if shutil.which("systemctl"):
|
|
165
|
+
_run(["systemctl", "--user", "disable", "--now", f"{render.UNIT}.timer"])
|
|
166
|
+
d = _systemd_dir()
|
|
167
|
+
for ext in ("service", "timer"):
|
|
168
|
+
(d / f"{render.UNIT}.{ext}").unlink(missing_ok=True)
|
|
169
|
+
if shutil.which("crontab"):
|
|
170
|
+
cur = _crontab_get()
|
|
171
|
+
if render.CRON_MARK in cur:
|
|
172
|
+
_run(["crontab", "-"], input=_strip_cron(cur))
|
|
173
|
+
return "Background checks removed."
|
|
174
|
+
if plat == "macos":
|
|
175
|
+
p = _plist_path()
|
|
176
|
+
_run(["launchctl", "bootout", f"{_launchd_domain()}/{render.LABEL}"])
|
|
177
|
+
p.unlink(missing_ok=True)
|
|
178
|
+
return "Background checks removed."
|
|
179
|
+
_run(windows_delete_args()) # fails harmlessly if it was never installed
|
|
180
|
+
return "Background checks removed."
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def status() -> str:
|
|
184
|
+
try:
|
|
185
|
+
plat = platform_name()
|
|
186
|
+
except RuntimeError as e:
|
|
187
|
+
return str(e)
|
|
188
|
+
if plat == "linux":
|
|
189
|
+
if (_systemd_dir() / f"{render.UNIT}.timer").exists():
|
|
190
|
+
en = _run(["systemctl", "--user", "is-enabled", f"{render.UNIT}.timer"])
|
|
191
|
+
ac = _run(["systemctl", "--user", "is-active", f"{render.UNIT}.timer"])
|
|
192
|
+
return (f"systemd timer: {ac.stdout.strip() or 'unknown'}, "
|
|
193
|
+
f"{en.stdout.strip() or 'unknown'}")
|
|
194
|
+
if shutil.which("crontab") and render.CRON_MARK in _crontab_get():
|
|
195
|
+
return "cron job installed"
|
|
196
|
+
return "not installed"
|
|
197
|
+
if plat == "macos":
|
|
198
|
+
if not _plist_path().exists():
|
|
199
|
+
return "not installed"
|
|
200
|
+
r = _run(["launchctl", "print", f"{_launchd_domain()}/{render.LABEL}"])
|
|
201
|
+
return "launchd agent loaded" if r.returncode == 0 else "launchd agent installed, not loaded"
|
|
202
|
+
r = _run(windows_query_args())
|
|
203
|
+
return "scheduled task installed" if r.returncode == 0 else "not installed"
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Pure renderers for scheduler files. Nothing here touches the system."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import shlex
|
|
5
|
+
import subprocess
|
|
6
|
+
from xml.sax.saxutils import escape as xesc
|
|
7
|
+
|
|
8
|
+
LABEL = "org.freemyid.recheck"
|
|
9
|
+
UNIT = "freemyid-recheck"
|
|
10
|
+
TASK = "Free My ID Recheck"
|
|
11
|
+
DAILY_HOUR = 10
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
ENV_PASSTHROUGH = ("FREEMYID_HOME", "FREEMYID_PASSPHRASE_FILE")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def systemd_service(cmd: list[str], env: dict[str, str] | None = None) -> str:
|
|
18
|
+
envs = "".join(f"Environment={_sd_quote(f'{k}={v}')}\n" for k, v in (env or {}).items())
|
|
19
|
+
return ("[Unit]\nDescription=Free My ID scheduled re-check (no UI, no email without approval)\n\n"
|
|
20
|
+
f"[Service]\nType=oneshot\nNice=10\n{envs}"
|
|
21
|
+
f"ExecStart={' '.join(_sd_quote(c) for c in cmd)}\n")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _sd_quote(s: str) -> str:
|
|
25
|
+
return '"' + s.replace("\\", "\\\\").replace('"', '\\"').replace("%", "%%") + '"'
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def systemd_timer() -> str:
|
|
29
|
+
# Persistent=true: a run missed while the machine was off/asleep fires at next boot/wake.
|
|
30
|
+
return ("[Unit]\nDescription=Run Free My ID re-check daily\n\n[Timer]\nOnCalendar=daily\n"
|
|
31
|
+
"Persistent=true\nRandomizedDelaySec=15min\n\n[Install]\nWantedBy=timers.target\n")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
CRON_MARK = "# freemyid-recheck"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def cron_lines(cmd: list[str]) -> str:
|
|
38
|
+
c = " ".join(shlex.quote(x) for x in cmd)
|
|
39
|
+
return (f"0 {DAILY_HOUR} * * * {c} {CRON_MARK}\n"
|
|
40
|
+
f"@reboot sleep 120 && {c} {CRON_MARK}\n")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def launchd_plist(cmd: list[str], log_path: str, env: dict[str, str] | None = None) -> str:
|
|
44
|
+
envx = ""
|
|
45
|
+
if env:
|
|
46
|
+
items = "".join(f" <key>{xesc(k)}</key>\n <string>{xesc(v)}</string>\n"
|
|
47
|
+
for k, v in env.items())
|
|
48
|
+
envx = f" <key>EnvironmentVariables</key>\n <dict>\n{items} </dict>\n"
|
|
49
|
+
args = "".join(f" <string>{xesc(c)}</string>\n" for c in cmd)
|
|
50
|
+
# launchd runs a job missed during sleep when the Mac wakes.
|
|
51
|
+
return ('<?xml version="1.0" encoding="UTF-8"?>\n'
|
|
52
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" '
|
|
53
|
+
'"http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict>\n'
|
|
54
|
+
f" <key>Label</key>\n <string>{LABEL}</string>\n{envx}"
|
|
55
|
+
f" <key>ProgramArguments</key>\n <array>\n{args} </array>\n"
|
|
56
|
+
" <key>StartCalendarInterval</key>\n <dict>\n"
|
|
57
|
+
f" <key>Hour</key>\n <integer>{DAILY_HOUR}</integer>\n"
|
|
58
|
+
" <key>Minute</key>\n <integer>0</integer>\n </dict>\n"
|
|
59
|
+
" <key>RunAtLoad</key>\n <false/>\n <key>ProcessType</key>\n <string>Background</string>\n"
|
|
60
|
+
f" <key>StandardErrorPath</key>\n <string>{xesc(log_path)}</string>\n"
|
|
61
|
+
f" <key>StandardOutPath</key>\n <string>{xesc(log_path)}</string>\n"
|
|
62
|
+
"</dict>\n</plist>\n")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def windows_task_xml(cmd: list[str], hour: int = DAILY_HOUR) -> str:
|
|
66
|
+
"""Per-user Task Scheduler XML (no admin needed).
|
|
67
|
+
|
|
68
|
+
Daily trigger; StartWhenAvailable = run as soon as possible after a missed start;
|
|
69
|
+
A windowed executable (FreeMyID.exe / pythonw.exe) = no console window flashes.
|
|
70
|
+
"""
|
|
71
|
+
exe, args = cmd[0], subprocess.list2cmdline(cmd[1:])
|
|
72
|
+
return ('<?xml version="1.0" encoding="UTF-16"?>\n'
|
|
73
|
+
'<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">\n'
|
|
74
|
+
" <RegistrationInfo><Description>Free My ID: quiet daily check for data-broker "
|
|
75
|
+
"listings. Only bothers you when it needs help.</Description></RegistrationInfo>\n"
|
|
76
|
+
" <Triggers>\n <CalendarTrigger>\n"
|
|
77
|
+
f" <StartBoundary>2000-01-01T{hour:02d}:00:00</StartBoundary>\n"
|
|
78
|
+
" <Enabled>true</Enabled>\n <ScheduleByDay><DaysInterval>1</DaysInterval>"
|
|
79
|
+
"</ScheduleByDay>\n </CalendarTrigger>\n </Triggers>\n"
|
|
80
|
+
" <Principals><Principal id=\"Author\"><LogonType>InteractiveToken</LogonType>"
|
|
81
|
+
"<RunLevel>LeastPrivilege</RunLevel></Principal></Principals>\n"
|
|
82
|
+
" <Settings>\n <StartWhenAvailable>true</StartWhenAvailable>\n"
|
|
83
|
+
" <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>\n"
|
|
84
|
+
" <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>\n"
|
|
85
|
+
" <RunOnlyIfNetworkAvailable>true</RunOnlyIfNetworkAvailable>\n"
|
|
86
|
+
" <WakeToRun>false</WakeToRun>\n <Hidden>false</Hidden>\n"
|
|
87
|
+
" <Enabled>true</Enabled>\n"
|
|
88
|
+
" <ExecutionTimeLimit>PT2H</ExecutionTimeLimit>\n"
|
|
89
|
+
" <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>\n </Settings>\n"
|
|
90
|
+
f" <Actions Context=\"Author\">\n <Exec>\n <Command>{xesc(exe)}</Command>\n"
|
|
91
|
+
f" <Arguments>{xesc(args)}</Arguments>\n </Exec>\n </Actions>\n</Task>\n")
|