ShellExecute to open an .exe in C++ - c++

I am trying to open sigverif.exe from my code in c++ but the return value is 2 and .exe does not open
ShellExecute(NULL, _T("open"), _T("C:\\Windows\\System32\\sigverif.exe"), NULL, NULL, SW_RESTORE);
If I open the sigverif.exe from run command typing
"C:\Windows\system32\sigverif.exe"
it works fine
What could be the issue?

the return value is 2 and .exe does not open
The return value based on System Error Codes means ERROR_FILE_NOT_FOUND.
Yes, indeed your application failed to find the given path, because you're building it on x86, where Windows's automatic redirection involves, and replace C:\Windows\System32 with C:\Windows\SysWOW64, which contains 32-bit binaries for Windows.
You have two options:
Either you just build it on x64, or disable the automatic redirection by using Wow64DisableWow64FsRedirection as follows:
PVOID OldValue = nullptr;
Wow64DisableWow64FsRedirection(&OldValue);
ShellExecute(NULL, _T("open"), _T("C:\\Windows\\System32\\sigverif.exe"), NULL, NULL, SW_RESTORE);
Be aware that Wow64DisableWow64FsRedirection affects globally in the current thread, as you can find more detail in the page:
Note The Wow64DisableWow64FsRedirection function affects all file operations performed by the current thread, which can have unintended consequences if file system redirection is disabled for any length of time....
So make sure it won't affect other operations unintentionally, or set it back to enabled immediately after your desire is resolved by invoking Wow64EnableWow64FsRedirection.

Related

Problems with some system default .lnk-files launching from under an impersonated user

I'm writing the 32bit service app where I want to be able to launch Start menu items for the logged users. I did manage to accomplish this task by impersonating user and launching selected .lnk-file using CreateProcessAsUser with command-line: %windir%\system32\cmd /c " start /b /i "" "<path-to-lnk-file>" ". And it works for almost every shortcut except bunch of the system shortcuts from Accessories folder (e.g. Sticky Notes.lnk, Snipping Tool.lnk). During the launch of the Snipping Tool I'm receiving the message box with this error from cmd:
Windows cannot find 'C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Accessories\Snipping Tool.lnk'. Make sure you typed the name correctly, and then try again.
But the .lnk-file exists in this very directory!
Summary:
service is 32 bit
Windows 8 Pro x64
launching shortcuts by user impersonation and CreateProcessAsUser with command-line %windir%\system32\cmd /c " start /b /i "" "<path-to-lnk-file>" "
approach works for almost every shortcut in Start menu except some in Start/Accessories folder (not all of them, e.g. Paint.lnk opens fine)
example code:
int launchAppForCurrentLoggedUser()
{
HANDLE userToken = WTSApiHelper::currentLoggedUserToken();
if (userToken == INVALID_HANDLE_VALUE) {
return -1;
}
//Duplicating token with access TOKEN_DUPLICATE | TOKEN_ALL_ACCESS,
//impersonation level SecurityImpersonation and token type TokenPrimary.
//Also closing original userToken
HANDLE dup = WTSApiHelper::duplicateToken(userToken);
if (dup == INVALID_HANDLE_VALUE) {
return -1;
}
int res = -1;
uint8 *env = NULL;
BOOL succeeded = CreateEnvironmentBlock((LPVOID *)&env, dup, FALSE);
if (!succeeded) {
Log("failed to get environment variables for user (error 0x%x).", GetLastError());
}
PROCESS_INFORMATION pi;
memset(&pi, 0, sizeof(PROCESS_INFORMATION));
STARTUPINFOW si;
memset(&si, 0, sizeof(STARTUPINFOW));
si.cb = sizeof(STARTUPINFOW);
si.lpDesktop = L"winsta0\\Default";
WCHAR params[] = L"/c \" start /b /i \"\" \"C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Accessories\\Snipping Tool.lnk\" \" ";
WCHAR cmd[] = L"C:\\Windows\\system32\\cmd.exe";
DWORD flags = env ? CREATE_UNICODE_ENVIRONMENT : 0;
succeeded = CreateProcessAsUserW(dup, cmd, params, NULL, NULL, FALSE, flags | CREATE_NO_WINDOW, env, NULL, &si, &pi);
if (!succeeded) {
Log("cannot launch process for user with error 0x%x.", GetLastError());
} else {
nres = 0;
}
DestroyEnvironmentBlock(env);
CloseHandle(dup);
return nres;
}
What do I miss here?
It's not the LNK file that is missing, but its target.
Seems like a WOW64 issue -- for your 32-bit service, %WINDIR%\System32 actually redirects to SysWOW64 and these executable files do not exist there.
Well, actually your 32-bit service is finding the 32-bit cmd.exe which does exist in SysWOW64, and then 32-bit cmd.exe has the above problem, when looking up the path %windir%\system32\SnippingTool.exe found in the shortcut .
I can reproduce the problem using a 32-bit Command Prompt. 32-bit processes attempting to use these shortcuts simply fail.
Try spawning the native version of cmd.exe (64-bit on your system), using %WINDIR%\SysNative\cmd.exe
In addition, you have quoting problems. You're trying to nest quotes, but what actually happens is that the second quote matches the first quote and exits quoting, rather than nesting.
In the future, when things fail in a service it is helpful to run the same call from a normal console application. In this case you would have immediately discovered that the issue is completely unrelated to impersonation. Second step, if it worked from a console application running in-profile would be using "Run As" with the console application, to test impersonation logic, still without the additional complexity of the service environment.
CreateProcessAsUser does not load the specified user's profile into the HKEY_USERS registry key. Therefore, to access the information in the HKEY_CURRENT_USER registry key, you must load the user's profile information into HKEY_USERS with the LoadUserProfile function before calling CreateProcessAsUser. Be sure to call UnloadUserProfile after the new process exits.
According to the msdn page
MSDN suggests to use CreateProcessWithLogonW or CreateProcessWithTokenW, or to manually load the user's profile information.
And also:
CreateProcessAsUser allows you to access the specified directory and executable image in the security context of the caller or the target user. By default, CreateProcessAsUser accesses the directory and executable image in the security context of the caller. In this case, if the caller does not have access to the directory and executable image, the function fails. To access the directory and executable image using the security context of the target user, specify hToken in a call to the ImpersonateLoggedOnUser function before calling CreateProcessAsUser.

ShellExecuteW not working well on Windows 8.1?

I call standard ShellExecuteW call on Windows8.1 to open PPS (powerpoint slide) file.
This works just fine on Windows 7. On Windows 8.1. it reports "There is no program associated to open the file". Of course, the file association is set and if file is saved and run from Explorer (double clicked) it opens just fine. I also tried to change association and to associate another program and then associate back to PPS viewer, no improvement. It just doesn't work for W8.1 but the same call works on earlier Windows.
Can anyone give me a clue what might be wrong here?
The code used to open file is very simple and I see no errors with it:
HINSTANCE hinst = ShellExecuteW(NULL, L"open", L"C:\\path\\to\\file.pps", NULL, NULL, SW_SHOWNORMAL);
// Check if result is error
if ((int)hinst <= 32)
{
wchar_t buf[512] = { 0 };
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, 512, NULL);
MSGBOX(buf);
}
I use free PPS viewer as found here:
http://www.microsoft.com/en-us/download/details.aspx?id=13
I found something similar which points to that this could be a bug in Win8.1. Can anyone confirm this? Or reveal a workaround?
I found the solution myself.
The problem on W8.1 was that the verb open was not registered to the application so it used different default verb. So if the ShellExecute call is replaced with:
HINSTANCE hinst = ShellExecuteW(NULL, NULL, L"C:\\path\\to\\file.pps", NULL, NULL, SW_SHOWNORMAL);
Then the system looks for a default verb which may or may not be open (usually is), so by not using this verb explicitly it leaves this decision to the system.

How can you open a file with the program associated with its file extension?

Is there a simple way to open a file by its associated program in windows?
(like double clicking it in windows explorer but done automatically with my code)
For example, on computer A, "text.txt" will be opened in wordpad but on computer B it will be opened by Notepad++ because of the users file extension assignments.
I tried ShellExecute
ShellExecute(0, L"open", L"c:\\windows\\notepad.exe" ,L"c:\\outfile.txt" , 0 , SW_SHOW );
which works but if I omit the notepad.exe parameter weird things happen (a random explorer is shown).
You want to use the file to open as the file argument, not the parameter argument. No need to specify which program to use, ShellExecute will look it up for you.
ShellExecute(0, 0, L"c:\\outfile.txt", 0, 0 , SW_SHOW );
By leaving the verb as NULL (0) rather than L"open", you get the true default action for the file type - usually this is open but not always.
See Launching Applications:
ShellExecute(NULL, "open", L"c:\\outfile.txt", NULL, NULL, SW_SHOW);
On windows, a good memory hook is to think of all data-files being executable by the shell. You can also try it out in a command box, where you can just type a filename, and it will be opened up. Or, the other way around, every file in Windows can be opened, and the default opening-action for executable files is to execute them.
According to the MS Knowledge Base, ShellExecute should work (we do this in Delphi all the time):
ShellExecute(Handle, "Open", Filename, "", "C:\", SW_SHOWNORMAL)
A little more possibilities here:
If you want to open - for example - the file by default with Notepad++ (if installed), you could scan for it's registry key if it exists and where it is, (Usually HKLM\SOFTWARE\Wow6432Node\Notepad++ [tested Win7]) then take that path and open it.
std::wstring file = L"C:\\Outfile.txt";
if (NotepadPlusPlusExists()) //Open with Notepad++ or use an other program... (maybe your own ?)
{
std::wstring wsNPPPath = GetNotepadPlusPlusPath();
ShellExecuteW(HWND, L"open", wsNPPPath.c_str(), file.c_str(), NULL, SW_NORMAL);
}
else //Open with default associated program <---
ShellExecuteW(HWND, NULL, file.c_str(), NULL, NULL, SW_NORMAL);
If you want the user to be able to change the default program or select a program he/she wants to use, you may open the "Open with" dialog.
//std::wstring StringArgsW(const wchar_t *format, ...);
std::wstring wsCmdOpenWith = StringArgsW(L"C:\\Windows\\system32\\shell32.dll,OpenAs_RunDLL \"%s\"", file.c_str());
ShellExecuteW(HWND, L"open", L"C:\\Windows\\system32\\rundll32.exe", wsCmdOpenWith.c_str(), NULL, SW_NORMAL);
You can also open the file in explorer.
std::wstring wsCmdExplorer = StringArgsW(L"/select,\"%s\"", file.c_str());
ShellExecuteW(HWND, L"open", L"explorer.exe", wsCmdExplorer.c_str(), NULL, SW_NORMAL);
If lpFile specifies a document file, the flag is simply passed to the
associated application
So you need to substitute "c:\\windows\\notepad.exe" with the actual file you want to open and leave lpParameters null.
Maybe try start instead of open?

64 bit windows file opening

I'm trying to use a dialog box to open a file in my program. This works perfectly on a 32 bit system, but when I try to use it on 64 bit it is unable to open the file. I've figured out that if the file trying to be opened is in the same directory as my program, it works fine. Trying to open a file from another folder, however, doesn't work at all.
So, I tried to copy the file to the program folder. This also works fine on 32 bit but doesn't work at all on a 64 system. Any thoughts why?
char cwdl[500];
getcwd(cwdl,500);
string mystring = string(cwdl);
CFileDialog fileDlg(TRUE, NULL, NULL, OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, "All Files (*.*)|*.*||", this);
fileDlg.m_ofn.lpstrTitle = "Select New File";
if( fileDlg.DoModal() == IDOK)
{
CString newFile= fileDlg.GetFileName();
mystring+="\\"+newFile;
const char * newLoc = mystring.c_str();
CopyFile(newFile,newLoc,true);
this is just a snippet of the code.
UAC and file system redirection are related yet different.
User account control is a permissions based security to prevent unauthorized users from changing your file system or executing applications which may affect other users. The prompt allows you to override the security by providing administrator privileges temporarily if that was your intent.
File system redirection is to allow backwards compatibility with 32bit applications by having a mirrored 32bit system folders and registry. In fact if the action causes UAC to kick in redirection does not occur it will always try to use the 64bit version of the file in that case. Unless you specify the redirection directory explicitly or run the 32bit application with administrator privileges to bypass UAC.
Ok that said you are using a relative path so it will look for the file in the current directory for the process. If it's compiled as 32 bit process running it on systems with different architectures may not behave as expected due to aforementioned redirection.
You can use GetCurrentDirectory windows API to see what directory the current process is using and verify it is what you expected. If not you have a few options.
The easiest would be to use fully qualified file paths.
You could also have two builds one targeted for each architecture you intend to deploy on. After all if you're on a 64bit system you might as well deploy 64bit applications.
A more involved option would be to subclass CFileDialog and disable redirection by calling Wow64DisableWow64FsRedirection in the constructor and Wow64RevertWow64FsRedirection in the desctructor. However this is meant to be a system setting so you may get new problems by forcing your 32bit application on 64bit windows.
There are probably plenty of other options as well since there is usually many ways to skin a cat. However the first step is to put some debug code in place and verify or eliminate redirection as a culprit with GetCurrentDirectory
Maybe it's just me, but I'm seeing a strange result: in 64-bit mode, the first four bytes of the buffer used to store the location of the path, are filled with zeros.
char wk[_MAX_PATH];
char *ret = _getcwd(wk, _MAX_PATH); // wk = "\0\0\0\0C:\\MyFolder\\..."
// ret = "C:\\MyFolder\\..."
The return value OTOH is correct. "ret" points to wk + 4;
In 32-bit mode, there are no leading zeros, the path starts at the first byte. Note: this is a Multi-byte app, not Unicode.
I'm using Visual Studio 2010 (10.0.40219.1 SP1Rel).
Anyway, if you're getting the same result, that would explain why your example doesn't work. You would have to say (in 64-bit mode only):
string mystring = string(cwdl + 4); // 64-bit only
Weird or What?
Edit: Seems to be an alignment problem. Works OK if the _getcwd is in a separate function.

Why does my code fail to create a directory in "C:\Program Files" under Windows 7?

I am using Windows 7 and I have to run one program in that windows but that program working in Windows XP. This is a Visual C++ program and I am using Visual Studio 2008 for this. When I am running my application, it does not throw any errors, but it does not create a directory in "c:\program files\". So can anyone help me to create directory and exe file?
This is the code I am using:
char szAppPath[MAX_PATH];
char szFileName[MAX_PATH];
DWORD dwResult;
WIN32_FIND_DATA FindFileData;
HANDLE hFind;
dwResult = ExpandEnvironmentStrings( NULL, szAppPath, MAX_PATH); // "%ProgramFiles%"
// do same for NSim directory
strcat(szAppPath,"\\NSim");
hFind = FindFirstFile(szAppPath, &FindFileData);
if (hFind == INVALID_HANDLE_VALUE)
{
//Directory Does't Exists create New
if(!CreateDirectory(szAppPath,NULL)) //Throw Error
{
MessageBox("Unable to Create N-SIM directory","NSim Installer");
return ;
}
}
else
{
//check if is directory or not
if(!(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
MessageBox("Can't Create N-SIM directory\n Another file with same name exists","NSim Installer");
return ;
}
FindClose(hFind);
}
//***************************************N-SIM Application****************************
strcpy(szFileName, szAppPath);
HRSRC hRes;
if( bRegister == FALSE)
{
strcat(szFileName,"\\NSim.exe"); //make same name of the Client & Server in program file
hRes = FindResource(NULL, MAKEINTRESOURCE(IDR_LANSIMSERVER),RT_RCDATA);
if(flagUpgrade ==0)
{
CString trial = installationDate(); //----- Detemine Expiry Date -----
setRegistry(trial);
}
}
It's a file permissions issue, plain and simple. Programs can't just go rooting around system directories in Windows 7. That's why it works "properly" in Windows XP, but not in newer versions.
I can't tell for sure, but it looks like you're trying to write an installer. If so, why are you reinventing the wheel? There are tons of great setup utilities availableā€”Visual Studio provides a setup project that you can customize to your needs, or look into Inno Setup, my personal favorite. A Google search will turn up plenty of other options that have already solved this problem for you, and innumerable others.
If this isn't an installer, and you're just trying to store application and/or user data in the Program Files folder, I highly recommend that you look elsewhere. You weren't supposed to shove data into the app folder under earlier versions of Windows, and Windows 7 just cuts you off at the knees if you do this. Your best bet is to follow the recommendations that existed from the beginning: Investigate the user and common Application Data folders carefully. Use the SHGetKnownFolderPath function to retrieve the full path to a known folder using its KNOWNFOLDERID. A couple of suggestions:
FOLDERID_ProgramData (a shared program data directory for all users)
FOLDERID_LocalAppData (a per-user program data directory, non-roaming)
FOLDERID_RoamingAppData (a per-user program data directory, roaming)
Alternatively, you can try running the application as an Administrator. You might want to look into creating a manifest that indicates the application requires administrator-level permissions to execute.
[edit] I edited the code in the question for readability and removed the commented out code (to see the wood for the trees). It is now obvious that nothing initialises szAppPath before calling strcat(), and calling ExpandEnvironmentStrings with NULL as the first argument is undefined (and certainly useless). Calling strcat() on an unitialised string is not likely to have the desired result. This may be an artefact of not posting the real code, or even of other peoples edits (including mine).
CreateDirectory sets the system error code on error; if you want to know what went wrong, check it! Any answer you get here will be an educated guess.
if(!CreateDirectory(szAppPath,NULL)) //Throw Error
{
DWORD errorcode = GetLastError();
LPVOID lpMsgBuf;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errorcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL );
MessageBox(NULL, (LPCTSTR)lpMsgBuf, TEXT("Error"), MB_OK);
return ;
}
If you just want to get the error code and look it up manually, then a complete directory of codes is available on MSDN, here, I would guess that ERROR_ACCESS_DENIED
(5) is most probable. A more elaborate example of error code display is given here.
windows7?
Ok, the problem is not with your program. Its with the file system permissions in Windows 7. User programs cannot create files there.
I think the problem is lack of privileges. You can debug your project to see whether the CreateDirectory function sets an error as ERROR_ACCESS_DENIED, if it does, you should make your program run with an administrator privilege. Add manifest in your project to do so.
It is intended to protect your computer against attack. Well maybe. Or Microsoft deciding to tell you what you are and not allowed to do on your own computer.
In any case you can change your UAC settings if you really have to write there in that way although that obviously exposes you to risk.
Otherwise play nice and do things the Microsoft way, using a proper installer.