Create Scheduled Task for a specific user - c++

I'm trying to make a little application that I can use at my work for setting up PC and Laptop demos. This application will run a series of operations. One of these operations is to create a Scheduled Task for the Customer account. The Customer account is created simply via NET USER Customer /ADD /PASSWORDREQ:NO (I have tried using the Windows API to achieve this, but I ran into a lot of problems. But that is for another question).
The Scheduled Task that is created for the Customer account would be issued like so: SCHTASKS /CREATE /TN DemoReboot /SC DAILY /ST 12:00 /ET 18:00 /MO 2 /TR "SHUTDOWN /R /F /T 30".
I'm aware of the /U and /P parameters, but unfortunately they do not work as credentials aren't allowed to be saved on the machine, and if there is no password provided, you are prompted to enter a password.
My current approach of executing the command above is like so:
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;
}
The way this function is called:
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();
}
I have left some comments in the code stating some tweaks I have tried.
After the command is executed, I log onto the Customer account and run SCHTASKS /DELETE /TN DemoReboot to see if the Task was successfully created, but the task name isn't found.
Next I tried using the Run function listed above, specifying just cmd as the command so I can get the command line for the Customer account. With the command line that pops up, I manually enter SCHTASKS /CREATE /TN DemoReboot /SC DAILY /ST 12:00 /ET 18:00 /MO 2 /TR "SHUTDOWN /R /F /T 30", then sign into the Customer account and try to delete the Task again, which does work; the task is there to be deleted!
There are other ways I have thought about doing this, such as using the Registry for the Customer account, adding keys under RunOnce, but I have not been able to successfully access the Registry of the Customer account from a different local account.
Hopefully I didn't leave anything out.
Update:
I have managed to figure out what the issue is. The first issue is that for the /MO Option, the taskModifierSpinBox has "hr" suffixed to the text. To fix that, I just split the QString like so: ui->taskModifierSpinBox.text().split(' ').first() What I should have done which I didn't think of just until now is use ui->taskModiferSpinBox->value().toString();
The other issue is the escaped double quotes... I don't really need them around the argument for /TN, but for /TR I do. I have tried using single quotes which did not work. I have tried doubling up on the double quotes, and that didn't work either. I also tried \\\" but that didn't work either.
What I have resulted to is to create a batch file, and pass the batch file as the command to run. Now that isn't really ideal, but I haven't figured out a way to successfully pass an argument that has spaces to /TR

Related

Opening powershell with ShellExecuteEX results in the PS session not importing all available modules

Problem: Launching Powershell with ShellExecuteEx is not importing all of the available modules correctly. Or, at least that's how it's presenting. I need to understand why iot's not importing them correctly and how to make the mavailable when going through ShellExecuteEx.
My overall goal is to launch specific predefined commands that require administrator privileges. An example is as follows:
New-LocalUser "Apples"
This is an arbitrary example but it is something that I tried and failed within the program. It's failing because it doesn't recognize New-LocalUser as a cmdlet. I ran the following command from within the Powershell session I created with ShellExecuteEx:
Get-Command -ListAvailable | Select-Object Name, Source
If I run the same command from a powershell that I open up and elevate manually (using my mouse lol). The list is almost the same but is larger. Comparing them shows that the list generated within my c++ program has 128 less commands. For instance, Microsoft.PowerShell.LocalAccounts is missing entirely
The function handling this in C++ is:
void MainWindow::execute_command()
{
QString tmp = "$p = \"Test62!""\"; $sec = $p | ConvertTo-SecureString -AsPlainText -Force; New-LocalUser \"Microshaft""\" -Password $sec";
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
SHELLEXECUTEINFO ShExecInfo = {0};
ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
ShExecInfo.hwnd = NULL;
ShExecInfo.lpVerb = "runas";
ShExecInfo.lpFile = TEXT("powershell.exe");
ShExecInfo.lpParameters = tmp.toUtf8();
ShExecInfo.lpDirectory = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Modules\\";
ShExecInfo.nShow = SW_SHOWNORMAL;
ShExecInfo.hInstApp = NULL;
ShellExecuteEx(&ShExecInfo);
WaitForSingleObject(ShExecInfo.hProcess,INFINITE);
}
Sestting:
ShExecInfo.lpDirectory = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Modules\\";
or
ShExecInfo.lpDirectory = NULL;
makes no difference. The list I generated was pulled by setting
ShExecInfo.lpParameters = "Get-Command -ListAvailable | Select-Object Name, Source > C:\\Users\\Aaron\\Downloads\\commands.txt";
I wanted to both both lists, or even a single list here but I can't because it exceeds teh character length. Can anyone explain why these commands are missing and how to mnake them available?
UPDATE:
So it's not bringing the commands over because if I install Microsoft.PowerShell.LocalAccounts I'm able to use them. It still fails when pushed out from a QString (probably how I'm escaping it). I left the session that I created within the program open. I copy-pasted my command over and, it runs fine.
I don't find the solution I've come up with as acceptable and am sure that I'm missing something. I shouldn't have to install any modules that already exist within the system. It must be how it's being created or opening for whatever reason it doesn't bring certain modules over. I also tried importing the module but, that didn't work either.
I'm going to Mark it as an answer because it does work. I still hope that someone else can provide me with a better answer or explanation.
If I install and import the module within the same command, it works fine. As I said in my update, I don't like or find this answer to be acceptable. It is an answer because it does work. I do hope that someone can assist by providing a better answer and or explanation.
QString example = "Install-Module -Name Microsoft.PowerShell.LocalAccounts; Import-Module Microsoft.Powershell.LocalAccounts; Get-Command -Module Microsoft.PowerShell.LocalAccounts > C:\\Users\\Aaron\\Downloads\\commands.txt; $p = \"Test62!""\"; $sec = $p | ConvertTo-SecureString -AsPlainText -Force; New-LocalUser \"Microshaft""\" -Password $sec"

C++ create process command line executing powershell command in command line arguments

I want to create process with something like this:
LPTSTR cmdArgs = _T("C:\\Windows\\System32\\cmd.exe PowerShell -Command Add-AppxPackage -Path \"C:\\Users\\TEST2\\abc.appx\" ");
call the API as below:
CreateProcess(NULL, cmdArgs, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)
Here when I do so, application is goin into deadlock.

Batch - User Input to Command Prompt (IF statement issue)

I am currently creating a segment of a larger batch file that needs to let the user interact with the command prompt, but I seem to be having issues with the IF Statements.
The following code is what I've been trying:
:ONE2
cls
echo Free Roam is used like command prompt, but with additional commands.
echo Type FRHELP for a list of additional commands.
:COMMANDLOOP
echo.
set /P TEMPCMD=%CD% :
IF %TEMPCMD% == QUIT (
GOTO END2
) else if %TEMPCMD% == FRHELP (
GOTO COMMANDSLIST
) else (
%TEMPCMD%
GOTO COMMANDLOOP
)
:COMMANDSLIST
echo.
echo FRHELP = Display Free Roam Commands
echo QUIT = Leave your current Free Roam Session
GOTO COMMANDLOOP
What happens is, I can make multi-part commands (EG: cd ..) without the IF statements. But with the IF statements, it will only allow me to make single part commands (EG: dir).
If I have the IF statements as listed above, it will give me the "'..' was unexpected" error and exit.
Is there any way to pass my TEMPCMD variable to the command prompt with these IF statements and not get this error?
try this, it works for me without any error:
#echo off &setlocal
:COMMANDLOOP
echo.
set "TEMPCMD=%CD%"
set /P "TEMPCMD=%CD% :"
IF "%TEMPCMD%"=="QUIT" (GOTO END2
) else (
if "%TEMPCMD%"=="FRHELP" (
GOTO COMMANDSLIST
) else (
GOTO COMMANDLOOP
)
)
pause
:COMMANDSLIST
echo.
echo FRHELP = Display Free Roam Commands
echo QUIT = Leave your current Free Roam Session
GOTO COMMANDLOOP

cmd command in wince7

I'm trying to run a cmd command in my wince7 machine.
my code is: (for example)
STARTUPINFOW siStartupInfo;
PROCESS_INFORMATION piProcessInfo;
memset(&siStartupInfo, 0, sizeof(siStartupInfo));
memset(&piProcessInfo, 0, sizeof(piProcessInfo));
siStartupInfo.cb = sizeof(siStartupInfo);
TCHAR regsvrActiveXConsole[256] = L"cd";
if (CreateProcess(L"\\Windows\\cmd.exe", regsvrActiveXConsole ,0,0,false,NULL,0,0,&siStartupInfo, &piProcessInfo))
{
}
else
{
}
and I get: "unrecognized option cd."
Do I miss something?
Do I need to add something to the image in order to run cmd commands?
cmd has certain usage rules, it doesn't just execute whatever you pass in arguments, type cmd /? in command prompt for more information. To make it execute a command, you need the /C option. The valid invocation would be:
cmd /C cd
i.e.
TCHAR regsvrActiveXConsole[256] = L"/C cd";

how to programmatically restart WAMP or Apache?

As part of some automated deploy + test scripts I use to verify programming done for a site, I have some scripts which update Apache's config files. I would like to programmatically restart WAMP so the changes take effect. Is there a good way to do this?
The scripts are powershell.
This is whats in my apache bin folder:
iconv
ab.exe
abs.exe
ApacheMonitor.exe
apr_dbd_odbc-1.dll
apr_ldap-1.dll
dbmmanage.pl
htcacheclean.exe
htdbm.exe
htdigest.exe
htpasswd.exe
httpd.exe
httxt2dbm.exe
libapr-1.dll
libapriconv-1.dll
libaprutil-1.dll
libeay32.dll
libhttpd.dll
logresolve.exe
openssl.exe
php.ini
php5isapi.dll
php5ts.dll
rotatelogs.exe
ssleay32.dll
wintty.exe
zlib1.dll
You can use this command to restart Wamp, Apache, MySQL services:
To start services
NET START wampapache
NET START wampmysqld
To stop services
NET STOP wampapache
NET STOP wampmysqld
For mariaDB, replace wampmysqld with wampmariadb.
For 64 bits: append 64 to the service names.
Simple execute command:
httpd.exe -k restart
ps. this is my wathdog for windows
#echo off
:loop
timeout /t 30 /nobreak
REM .
tasklist /FI "IMAGENAME eq php-cgi.exe" 2>NUL | find /I /N "php-cgi.exe">NUL
if "%ERRORLEVEL%"=="1" goto Process_NotFound
tasklist /FI "IMAGENAME eq httpd.exe" 2>NUL | find /I /N "httpd.exe">NUL
if "%ERRORLEVEL%"=="1" goto Process_NotFound
goto loop
:Process_NotFound
TASKKILL /F /IM php-cgi.exe
TASKKILL /F /IM httpd.exe
ping 127.0.0.1 -n 2
Apache -k start
ping 127.0.0.1 -n 3
cls
php.exe -r "$ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'http://server.name/'); curl_setopt($ch, CURLOPT_VERBOSE, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_exec($ch);"
ping 127.0.0.1 -n 3
ab.exe -n 10 -c 3 http://server.name/
goto loop
CTRL+R -> Type (Command) -> Right Mouse -> Run Administrator
Go to your wamp aptech bin folder eg : D:\wamp\bin\apache\apache2.4.9\bin>
Type httpd.exe -d (Show All apache parameter command )
httpd.exe -k start -n wampapache64
httpd.exe -k stop -n wampapache64
httpd.exe -k restart -n wampapache64
Graphical Instruction :
Step First:
Step Second:
I ended up writing some code to find the "wampapache" service and restarting it.
public static void ResetApache()
{
ServiceUtil.RestartService("wampapache", 10000);
}
...
public class ServiceUtil
{
public static void RestartService(string serviceName, int msTimeout)
{
ServiceController service = new ServiceController(serviceName);
int startTicks = Environment.TickCount;
TimeSpan timeout = TimeSpan.FromMilliseconds(msTimeout);
if (service.Status != ServiceControllerStatus.Stopped
&& service.Status != ServiceControllerStatus.StopPending)
{
service.Stop();
}
service.WaitForStatus(ServiceControllerStatus.Stopped, timeout);
int midTicks = Environment.TickCount;
timeout = TimeSpan.FromMilliseconds(msTimeout - (midTicks - startTicks));
service.Start();
service.WaitForStatus(ServiceControllerStatus.Running, timeout);
//int finalTicks = Environment.TickCount;
//var totalTime = TimeSpan.FromTicks(finalTicks - startTicks);
//Console.WriteLine("Reseting process took " + (totalTime.TotalMilliseconds/1000.0) + " seconds.");
}
}