Privilege management on Windows is always a tricky part. MSoft assumes that normal mortals should not care with that and documentation is often incomplete.
Here you problem is likely that normal processes (not running as administrator) have not the privilege to change the system time.
But what is possible is:
- check whether the SeSystemtimePrivilege is granted to the running process
- if it is just set the time
- if not restart the process with same parameter as administrator via
ShellExectute
, verb="runas"
Here is an example of code (the part to parse the time string and actually set the time is not implemented):
#include <iostream>
#include <windows.h>
#include <tchar.h>
#ifdef UNICODE
#define tcout std::wcout
#else
#define tcout std::cout
#endif
// utility used to display error messages
void errmsg() {
DWORD err = ::GetLastError();
LPTSTR msg;
::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL,
err, LANG_NEUTRAL, (LPTSTR) &msg, 0, NULL);
tcout << _T("Error") << std::endl;
}
int _tmain(int argc, TCHAR *argv[]) {
// syntax check
if (argc != 2) {
tcout << _T("Usage ") << argv[0] << " _time_" << std::endl;
return 1;
}
LPCTSTR time = argv[1];
// get access to the current process token
HANDLE process = ::GetCurrentProcess();
HANDLE token;
if (! ::OpenProcessToken(process, TOKEN_ALL_ACCESS, &token)) {
errmsg();
return 1;
}
// get priviledges from current token
DWORD sz = 0, sz2;
TOKEN_PRIVILEGES *privs;
::GetTokenInformation(token, TokenPrivileges, NULL, 0, &sz); // first only size
if (sz == 0) {
errmsg();
return 1;
}
privs = (TOKEN_PRIVILEGES *) malloc(sz);
if (! ::GetTokenInformation(token, TokenPrivileges, privs, sz, &sz2)) {
errmsg();
return 1;
}
// display found priviledges and look if SE_SYSTEMTIME_NAME is present
BOOL ok = FALSE;
for (size_t i=0; i<privs->PrivilegeCount; i++) {
sz = 0;
::LookupPrivilegeName(NULL, &(privs->Privileges[i].Luid), NULL, &sz);
LPTSTR name = (LPTSTR) malloc(sz * sizeof(TCHAR));
if (! ::LookupPrivilegeName(NULL, &(privs->Privileges[i].Luid), name, &sz)) {
errmsg();
return 1;
}
if (0 == ::lstrcmp(name, SE_SYSTEMTIME_NAME)) {
ok = TRUE;
}
// tcout << name << std::endl;
free(name);
}
free(privs); // done with it
tcout << (ok ? _T("Can") : _T("Cannot")) << _T(" change time") << std::endl;
if (ok) {
tcout << _T("Setting time with ") << time << std::endl;
// actually parse the time string and call SetSystemTime
//...
}
else {
tcout << _T("Restart self as admin...") << std::endl;
// start as cmd /K "full path" to have a chance to see eventual errors
LPTSTR params = (LPTSTR) malloc(MAX_PATH + 7 + lstrlen(time));
lstrcpy(params, _T(" /K \""));
sz = ::GetModuleFileName(NULL, params + 5, MAX_PATH);
// tcout << params + 5 << std::endl;
::lstrcat(params,_T("\" ")); // do not forget the trailing "
lstrcat(params, time);
// Let's go, the UAC control should pop to allow the privilege elevation
if (((int) ShellExecute(NULL, _T("runas"), _T("cmd.exe"), params,
_T("."), SW_SHOW)) < 32) {
tcout << _T("Could not start self with elevated privileges") << std::endl;
return 1;
}
free(params);
}
// time to clean...
::CloseHandle(token);
::CloseHandle(process);
return 0;
}
Things to still work on:
- when the command is restarted in admin mode, is uses a console with
cmd /K
to allow user to see what as happened. If you prefere to be more silent, you should use cmd /C
instead and use SW_HIDE
is ShellExecute
.
- this program assumes that the new time will be given as a simple string which may or not be relevant for your use case
- the verbosity should be controlled with optional arguments
- a system should prevent to try runas admin more than once (probably a second or optional parameter)