首页 > 解决方案 > 为特定用户创建计划任务

问题描述

我正在尝试制作一个可以在工作中用于设置 PC 和笔记本电脑演示的小应用程序。此应用程序将运行一系列操作。其中一项操作是为客户帐户创建计划任务。客户帐户是通过简单地创建的NET USER Customer /ADD /PASSWORDREQ:NO(我曾尝试使用 Windows API 来实现这一点,但我遇到了很多问题。但这是另一个问题)。

为客户帐户创建的计划任务将像这样发出:SCHTASKS /CREATE /TN DemoReboot /SC DAILY /ST 12:00 /ET 18:00 /MO 2 /TR "SHUTDOWN /R /F /T 30"

我知道/U/P参数,但不幸的是它们不起作用,因为不允许将凭据保存在机器上,如果没有提供密码,系统会提示您输入密码。

我目前执行上述命令的方法是这样的:

bool AccountManager::run(const QString &rUserName, const QString &rPass, const QString &rCommand)
{
    HANDLE    hToken;
    LPVOID    lpvEnv;
    PROCESS_INFORMATION pi = {0};
    STARTUPINFO         si = {0};
    si.cb = sizeof(STARTUPINFO);

    std::wstring wUser = rUserName.toStdWString();
    WCHAR *pUser = const_cast<WCHAR*>(wUser.c_str());

    std::wstring wPass = rPass.toStdWString();
    WCHAR *pPass = const_cast<WCHAR*>(wPass.c_str());

    std::wstring wCommand = rCommand.toStdWString();
    WCHAR *pCommand = const_cast<WCHAR*>(wCommand.c_str());
    /*
    if (!CreateProcessWithLogonW(test, L".", pass,
            0, NULL, cmd,
            CREATE_UNICODE_ENVIRONMENT, NULL, NULL,
            &si, &pi))
        DisplayError(str);
        */
    if (!LogonUser(pUser, L".", pPass, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &hToken)) {
        setLastError("LogonUser");
        return false;
    }

    /*
    if (!ImpersonateLoggedOnUser(&hToken))
    {
        setLastError("ImpersonateLoggedOnUser");
        return false;
    }
    */

    if (!CreateEnvironmentBlock(&lpvEnv, hToken, TRUE)) {
        setLastError("CreateEnvironmentBlock");
        return false;
    }

    // I've tried specifying NULL for the second paramater (lpDomain)
    if (!CreateProcessWithLogonW(pUser, L".", pPass,
                                 LOGON_WITH_PROFILE, NULL, pCommand,
                                 // I've tried using the created environmentBlock
                                 //CREATE_UNICODE_ENVIRONMENT, lpvEnv, NULL,
                                 CREATE_UNICODE_ENVIRONMENT, NULL, NULL,
                                 &si, &pi)) {
        setLastError("CreateProcessWithLogonW");
        return false;
    }

    if (!DestroyEnvironmentBlock(lpvEnv)) {
        setLastError("DestroyEnvironmentBlock");
        return false;
    }

    CloseHandle(hToken);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    qDebug() << "finished";
    return true;
}

这个函数的调用方式:

void MainWindow::onCreateRebootTaskClicked()
{
    // Memory Leak
    QProcess *pProc = new QProcess();
    pProc->setProgram("cmd.exe");
    // Blank password is not allowed, so add temp one
    pProc->setArguments({QString("/C net user %1 %2").arg(ui->cxNameLineEdit->text(), "cccx") });
    pProc->start();
    pProc->waitForFinished();

    // Tried without using cmd /C
    const QString command = QString("cmd /C SCHTASKS /CREATE /TN \"%1\" /SC DAILY /ST %2 /ET %3 /MO %4 /TR \"%5\"").arg(
                                         ui->taskRebootNameLineEdit->text(),
                                         ui->taskStartTimeEdit->text(),
                                         ui->taskEndTimeEdit->text(),
                                         ui->taskModifierSpinBox->text(),
                                         QString("shutdown /r /f /t %1").arg(ui->taskRebootTimeOutSpinBox->value()));

    if (!AccountManager::run(ui->cxNameLineEdit->text(), "cccx", command))
    {
        const auto &rError = AccountManager::getLastError();
        QMessageBox::warning(this, rError.errorCodeName, rError.msg);
    }

    // Remove temp password
    pProc->setArguments({QString("/C net user %1 %2").arg(ui->cxNameLineEdit->text(), "") });
    pProc->start();
}

我在代码中留下了一些注释,说明了我尝试过的一些调整。

执行完命令后,我登录Customer账号运行SCHTASKS /DELETE /TN DemoReboot,查看Task是否创建成功,但是没有找到任务名。

接下来,我尝试使用Run上面列出的函数,将其指定cmd为命令,以便获取客户帐户的命令行。随着弹出的命令行,我手动输入SCHTASKS /CREATE /TN DemoReboot /SC DAILY /ST 12:00 /ET 18:00 /MO 2 /TR "SHUTDOWN /R /F /T 30",然后登录客户帐户并尝试再次删除任务,这确实有效;任务在那里被删除!

我还考虑过其他方法,例如使用客户帐户的注册表,在 下添加密钥RunOnce,但我无法从不同的本地帐户成功访问客户帐户的注册表。

希望我没有遗漏任何东西。

更新:我已经设法找出问题所在。第一个问题是对于/MO选项,taskModifierSpinBox文本后缀有“hr”。为了解决这个问题,我只是QString这样拆分:ui->taskModifierSpinBox.text().split(' ').first() 我应该做的,直到现在我才想到的是使用ui->taskModiferSpinBox->value().toString();

另一个问题是转义的双引号......我真的不需要它们围绕参数 for /TN,但/TR我需要。我尝试过使用不起作用的单引号。我曾尝试在双引号上加倍,但这也不起作用。我也试过\\\",但也没有用。

我的结果是创建一个批处理文件,并将批处理文件作为命令传递给run. 现在这不是很理想,但我还没有想出一种方法来成功传递一个有空格的参数/TR

标签: c++qtwinapiimpersonationwindows-task-scheduler

解决方案


推荐阅读