Files
PowerToys/src/runner/auto_start_helper.cpp
Josh Soref bf16e10baf Updates for check-spelling v0.0.25 (#40386)
## Summary of the Pull Request

- #39572 updated check-spelling but ignored:
   > 🐣 Breaking Changes
[Code Scanning action requires a Code Scanning
Ruleset](https://github.com/check-spelling/check-spelling/wiki/Breaking-Change:-Code-Scanning-action-requires-a-Code-Scanning-Ruleset)
If you use SARIF reporting, then instead of the workflow yielding an 
when it fails, it will rely on [github-advanced-security
🤖](https://github.com/apps/github-advanced-security) to report the
failure. You will need to adjust your checks for PRs.

This means that check-spelling hasn't been properly doing its job 😦.

I'm sorry, I should have pushed a thing to this repo earlier,...

Anyway, as with most refreshes, this comes with a number of fixes, some
are fixes for typos that snuck in before the 0.0.25 upgrade, some are
for things that snuck in after, some are based on new rules in
spell-check-this, and some are hand written patterns based on running
through this repository a few times.

About the 🐣 **breaking change**: someone needs to create a ruleset for
this repository (see [Code Scanning action requires a Code Scanning
Ruleset: Sample ruleset

](https://github.com/check-spelling/check-spelling/wiki/Breaking-Change:-Code-Scanning-action-requires-a-Code-Scanning-Ruleset#sample-ruleset)).

The alternative to adding a ruleset is to change the condition to not
use sarif for this repository. In general, I think the github
integration from sarif is prettier/more helpful, so I think that it's
the better choice.

You can see an example of it working in:
- https://github.com/check-spelling-sandbox/PowerToys/pull/23

---------

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
Co-authored-by: Mike Griese <migrie@microsoft.com>
Co-authored-by: Dustin L. Howett <dustin@howett.net>
2025-07-08 17:16:52 -05:00

398 lines
14 KiB
C++

#include "pch.h"
#include "auto_start_helper.h"
#include <Lmcons.h>
#include <comdef.h>
#include <taskschd.h>
#include <common/logger/logger.h>
// Helper macros from wix.
#define ExitOnFailure(x, s, ...) \
if (FAILED(x)) \
{ \
Logger::error(s, ##__VA_ARGS__); \
goto LExit; \
}
#define ExitWithLastError(x, s, ...) \
{ \
DWORD util_err = ::GetLastError(); \
x = HRESULT_FROM_WIN32(util_err); \
if (!FAILED(x)) \
{ \
x = E_FAIL; \
} \
Logger::error(s, ##__VA_ARGS__); \
goto LExit; \
}
#define ExitFunction() \
{ \
goto LExit; \
}
const DWORD USERNAME_DOMAIN_LEN = DNLEN + UNLEN + 2; // Domain Name + '\' + User Name + '\0'
const DWORD USERNAME_LEN = UNLEN + 1; // User Name + '\0'
bool create_auto_start_task_for_this_user(bool runElevated)
{
HRESULT hr = S_OK;
WCHAR username_domain[USERNAME_DOMAIN_LEN];
WCHAR username[USERNAME_LEN];
std::wstring wstrTaskName;
ITaskService* pService = NULL;
ITaskFolder* pTaskFolder = NULL;
ITaskDefinition* pTask = NULL;
IRegistrationInfo* pRegInfo = NULL;
ITaskSettings* pSettings = NULL;
ITriggerCollection* pTriggerCollection = NULL;
IRegisteredTask* pRegisteredTask = NULL;
// ------------------------------------------------------
// Get the Domain/Username for the trigger.
if (!GetEnvironmentVariable(L"USERNAME", username, USERNAME_LEN))
{
ExitWithLastError(hr, "Getting username failed: {:x}", hr);
}
if (!GetEnvironmentVariable(L"USERDOMAIN", username_domain, USERNAME_DOMAIN_LEN))
{
ExitWithLastError(hr, "Getting the user's domain failed: {:x}", hr);
}
wcscat_s(username_domain, L"\\");
wcscat_s(username_domain, username);
// Task Name.
wstrTaskName = L"Autorun for ";
wstrTaskName += username;
// Get the executable path passed to the custom action.
WCHAR wszExecutablePath[MAX_PATH];
GetModuleFileName(NULL, wszExecutablePath, MAX_PATH);
// ------------------------------------------------------
// Create an instance of the Task Service.
hr = CoCreateInstance(CLSID_TaskScheduler,
NULL,
CLSCTX_INPROC_SERVER,
IID_ITaskService,
reinterpret_cast<void**>(&pService));
ExitOnFailure(hr, "Failed to create an instance of ITaskService: {:x}", hr);
// Connect to the task service.
hr = pService->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());
ExitOnFailure(hr, "ITaskService::Connect failed: {:x}", hr);
// ------------------------------------------------------
// Get the PowerToys task folder. Creates it if it doesn't exist.
hr = pService->GetFolder(_bstr_t(L"\\PowerToys"), &pTaskFolder);
if (FAILED(hr))
{
// Folder doesn't exist. Get the Root folder and create the PowerToys subfolder.
ITaskFolder* pRootFolder = NULL;
hr = pService->GetFolder(_bstr_t(L"\\"), &pRootFolder);
ExitOnFailure(hr, "Cannot get Root Folder pointer: {:x}", hr);
hr = pRootFolder->CreateFolder(_bstr_t(L"\\PowerToys"), _variant_t(L""), &pTaskFolder);
if (FAILED(hr))
{
pRootFolder->Release();
ExitOnFailure(hr, "Cannot create PowerToys task folder: {:x}", hr);
}
}
// If the task exists, just enable it.
{
IRegisteredTask* pExistingRegisteredTask = NULL;
hr = pTaskFolder->GetTask(_bstr_t(wstrTaskName.c_str()), &pExistingRegisteredTask);
if (SUCCEEDED(hr))
{
// Task exists, try enabling it.
hr = pExistingRegisteredTask->put_Enabled(VARIANT_TRUE);
pExistingRegisteredTask->Release();
if (SUCCEEDED(hr))
{
// Function enable. Sounds like a success.
ExitFunction();
}
}
}
// Create the task builder object to create the task.
hr = pService->NewTask(0, &pTask);
ExitOnFailure(hr, "Failed to create a task definition: {:x}", hr);
// ------------------------------------------------------
// Get the registration info for setting the identification.
hr = pTask->get_RegistrationInfo(&pRegInfo);
ExitOnFailure(hr, "Cannot get identification pointer: {:x}", hr);
hr = pRegInfo->put_Author(_bstr_t(username_domain));
ExitOnFailure(hr, "Cannot put identification info: {:x}", hr);
// ------------------------------------------------------
// Create the settings for the task
hr = pTask->get_Settings(&pSettings);
ExitOnFailure(hr, "Cannot get settings pointer: {:x}", hr);
hr = pSettings->put_StartWhenAvailable(VARIANT_FALSE);
ExitOnFailure(hr, "Cannot put_StartWhenAvailable setting info: {:x}", hr);
hr = pSettings->put_StopIfGoingOnBatteries(VARIANT_FALSE);
ExitOnFailure(hr, "Cannot put_StopIfGoingOnBatteries setting info: {:x}", hr);
hr = pSettings->put_ExecutionTimeLimit(_bstr_t(L"PT0S")); //Unlimited
ExitOnFailure(hr, "Cannot put_ExecutionTimeLimit setting info: {:x}", hr);
hr = pSettings->put_DisallowStartIfOnBatteries(VARIANT_FALSE);
ExitOnFailure(hr, "Cannot put_DisallowStartIfOnBatteries setting info: {:x}", hr);
hr = pSettings->put_Priority(4);
ExitOnFailure(hr, "Cannot put_Priority setting info : {:x}", hr);
// ------------------------------------------------------
// Get the trigger collection to insert the logon trigger.
hr = pTask->get_Triggers(&pTriggerCollection);
ExitOnFailure(hr, "Cannot get trigger collection: {:x}", hr);
// Add the logon trigger to the task.
{
ITrigger* pTrigger = NULL;
ILogonTrigger* pLogonTrigger = NULL;
hr = pTriggerCollection->Create(TASK_TRIGGER_LOGON, &pTrigger);
ExitOnFailure(hr, "Cannot create the trigger: {:x}", hr);
hr = pTrigger->QueryInterface(
IID_ILogonTrigger, reinterpret_cast<void**>(&pLogonTrigger));
pTrigger->Release();
ExitOnFailure(hr, "QueryInterface call failed for ILogonTrigger: {:x}", hr);
hr = pLogonTrigger->put_Id(_bstr_t(L"Trigger1"));
// Timing issues may make explorer not be started when the task runs.
// Add a little delay to mitigate this.
hr = pLogonTrigger->put_Delay(_bstr_t(L"PT03S"));
// Define the user. The task will execute when the user logs on.
// The specified user must be a user on this computer.
hr = pLogonTrigger->put_UserId(_bstr_t(username_domain));
pLogonTrigger->Release();
ExitOnFailure(hr, "Cannot add user ID to logon trigger: {:x}", hr);
}
// ------------------------------------------------------
// Add an Action to the task. This task will execute the path passed to this custom action.
{
IActionCollection* pActionCollection = NULL;
IAction* pAction = NULL;
IExecAction* pExecAction = NULL;
// Get the task action collection pointer.
hr = pTask->get_Actions(&pActionCollection);
ExitOnFailure(hr, "Cannot get Task collection pointer: {:x}", hr);
// Create the action, specifying that it is an executable action.
hr = pActionCollection->Create(TASK_ACTION_EXEC, &pAction);
pActionCollection->Release();
ExitOnFailure(hr, "Cannot create the action: {:x}", hr);
// QI for the executable task pointer.
hr = pAction->QueryInterface(
IID_IExecAction, reinterpret_cast<void**>(&pExecAction));
pAction->Release();
ExitOnFailure(hr, "QueryInterface call failed for IExecAction: {:x}", hr);
// Set the path of the executable to PowerToys (passed as CustomActionData).
hr = pExecAction->put_Path(_bstr_t(wszExecutablePath));
pExecAction->Release();
ExitOnFailure(hr, "Cannot set path of executable: {:x}", hr);
}
// ------------------------------------------------------
// Create the principal for the task
{
IPrincipal* pPrincipal = NULL;
hr = pTask->get_Principal(&pPrincipal);
ExitOnFailure(hr, "Cannot get principal pointer: {:x}", hr);
// Set up principal information:
hr = pPrincipal->put_Id(_bstr_t(L"Principal1"));
hr = pPrincipal->put_UserId(_bstr_t(username_domain));
hr = pPrincipal->put_LogonType(TASK_LOGON_INTERACTIVE_TOKEN);
if (runElevated)
{
hr = pPrincipal->put_RunLevel(_TASK_RUNLEVEL::TASK_RUNLEVEL_HIGHEST);
}
else
{
hr = pPrincipal->put_RunLevel(_TASK_RUNLEVEL::TASK_RUNLEVEL_LUA);
}
pPrincipal->Release();
ExitOnFailure(hr, "Cannot put principal run level: {:x}", hr);
}
// ------------------------------------------------------
// Save the task in the PowerToys folder.
{
_variant_t SDDL_FULL_ACCESS_FOR_EVERYONE = L"D:(A;;FA;;;WD)";
hr = pTaskFolder->RegisterTaskDefinition(
_bstr_t(wstrTaskName.c_str()),
pTask,
TASK_CREATE_OR_UPDATE,
_variant_t(username_domain),
_variant_t(),
TASK_LOGON_INTERACTIVE_TOKEN,
SDDL_FULL_ACCESS_FOR_EVERYONE,
&pRegisteredTask);
ExitOnFailure(hr, "Error saving the Task : {:x}", hr);
}
LExit:
if (pService)
pService->Release();
if (pTaskFolder)
pTaskFolder->Release();
if (pTask)
pTask->Release();
if (pRegInfo)
pRegInfo->Release();
if (pSettings)
pSettings->Release();
if (pTriggerCollection)
pTriggerCollection->Release();
if (pRegisteredTask)
pRegisteredTask->Release();
return (SUCCEEDED(hr));
}
bool delete_auto_start_task_for_this_user()
{
HRESULT hr = S_OK;
WCHAR username[USERNAME_LEN];
std::wstring wstrTaskName;
ITaskService* pService = NULL;
ITaskFolder* pTaskFolder = NULL;
// ------------------------------------------------------
// Get the Username for the task.
if (!GetEnvironmentVariable(L"USERNAME", username, USERNAME_LEN))
{
ExitWithLastError(hr, "Getting username failed: {:x}", hr);
}
// Task Name.
wstrTaskName = L"Autorun for ";
wstrTaskName += username;
// ------------------------------------------------------
// Create an instance of the Task Service.
hr = CoCreateInstance(CLSID_TaskScheduler,
NULL,
CLSCTX_INPROC_SERVER,
IID_ITaskService,
reinterpret_cast<void**>(&pService));
ExitOnFailure(hr, "Failed to create an instance of ITaskService: {:x}", hr);
// Connect to the task service.
hr = pService->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());
ExitOnFailure(hr, "ITaskService::Connect failed: {:x}", hr);
// ------------------------------------------------------
// Get the PowerToys task folder.
hr = pService->GetFolder(_bstr_t(L"\\PowerToys"), &pTaskFolder);
if (FAILED(hr))
{
// Folder doesn't exist. No need to disable a non-existing task.
hr = S_OK;
ExitFunction();
}
// ------------------------------------------------------
// If the task exists, disable.
{
IRegisteredTask* pExistingRegisteredTask = NULL;
hr = pTaskFolder->GetTask(_bstr_t(wstrTaskName.c_str()), &pExistingRegisteredTask);
if (SUCCEEDED(hr))
{
// Task exists, try disabling it.
hr = pTaskFolder->DeleteTask(_bstr_t(wstrTaskName.c_str()), 0);
}
}
LExit:
if (pService)
pService->Release();
if (pTaskFolder)
pTaskFolder->Release();
return (SUCCEEDED(hr));
}
bool is_auto_start_task_active_for_this_user()
{
HRESULT hr = S_OK;
WCHAR username[USERNAME_LEN];
std::wstring wstrTaskName;
ITaskService* pService = NULL;
ITaskFolder* pTaskFolder = NULL;
// ------------------------------------------------------
// Get the Username for the task.
if (!GetEnvironmentVariable(L"USERNAME", username, USERNAME_LEN))
{
ExitWithLastError(hr, "Getting username failed: {:x}", hr);
}
// Task Name.
wstrTaskName = L"Autorun for ";
wstrTaskName += username;
// ------------------------------------------------------
// Create an instance of the Task Service.
hr = CoCreateInstance(CLSID_TaskScheduler,
NULL,
CLSCTX_INPROC_SERVER,
IID_ITaskService,
reinterpret_cast<void**>(&pService));
ExitOnFailure(hr, "Failed to create an instance of ITaskService: {:x}", hr);
// Connect to the task service.
hr = pService->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());
ExitOnFailure(hr, "ITaskService::Connect failed: {:x}", hr);
// ------------------------------------------------------
// Get the PowerToys task folder.
hr = pService->GetFolder(_bstr_t(L"\\PowerToys"), &pTaskFolder);
ExitOnFailure(hr, "ITaskFolder doesn't exist: {:x}", hr);
// ------------------------------------------------------
// If the task exists, disable.
{
IRegisteredTask* pExistingRegisteredTask = NULL;
hr = pTaskFolder->GetTask(_bstr_t(wstrTaskName.c_str()), &pExistingRegisteredTask);
if (SUCCEEDED(hr))
{
// Task exists, get its value.
VARIANT_BOOL is_enabled;
hr = pExistingRegisteredTask->get_Enabled(&is_enabled);
pExistingRegisteredTask->Release();
if (SUCCEEDED(hr))
{
// Got the value. Return it.
hr = (is_enabled == VARIANT_TRUE) ? S_OK : E_FAIL; // Fake success or fail to return the value.
ExitFunction();
}
}
}
LExit:
if (pService)
pService->Release();
if (pTaskFolder)
pTaskFolder->Release();
return (SUCCEEDED(hr));
}