C/C++ CreateFileA fails - c++

I am trying to make an image viewer, and everything works when I hard code the path of the file, but when I try to get the path so I can open files with it, it doesn't work, nothing happens.
When debugging, I found out that CreateFileA returns INVALID_HANDLE_VALUE.
This leads me to believe that the error should be in these 2 lines of code.
LPSTR FileA = GetCommandLine();
HANDLE FileHandle = CreateFileA(FileA, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);

When CreateFileA() returns INVALID_HANDLE_VALUE on failure, use GetLastError() to find out WHY it failed. But in this case, GetCommandLine() is likely not returning the path you are expecting.
Assuming that is actually calling the Win32 GetCommandLineA() function, and not some other function in your program, then that function returns the command line that was used to launch the calling process, ie "C:\<path>\myapp.exe <parameters>". If there are no <parameters> present, then you would be trying to open your own program's EXE, not an image file. And if there are <parameters> present, then you would be passing an invalid file path to CreateFileA().
You need to parse the command line to extract just the file path you actually want, THEN you can try to open the file at that path. Have a look at Parsing C Command-Line Arguments and Parsing C++ Command-Line Arguments . Consider using CommandLineToArgvW() to make that easier.

Related

Cygwin: Deleting file when handle is opened

I have a file which I want to delete, it's handle is held by system process so everytime I try to delete it it gives Access denied but for some reason cygwin is able to delete it.
I've downloaded the coreutils and investigated the source code of rm executable and found that it uses unlink function to achieve it. I've created a little test program which uses same function but it gives me Access denied anyway.
Then I found this article and guy describes how cygwin is able to delete a file which is the following:
Cygwin opens files always with all sharing flags
set, so a file opened by a Cygwin process should not result in a sharing
violation in another open call. The exception is the first NtOpenFile
in unlink_nt, which opens the file with FILE_SHARE_DELETE only to find
out if the file has an open handle somewhere else. In that case it gets
a STATUS_SHARING_VIOLATION, the next NtOpenFile will open the file with
all sharing flags set, and unlink_nt will try to delete the file or to
rename it, or to move it to the recycle bin, dependent on its path.
Which makes sense, So I started to implement same thing. Here is my code:
HANDLE file;
PIO_STATUS_BLOCK stat;
UNICODE_STRING myUnicodeStr;
RtlInitUnicodeString(&myUnicodeStr, L"C:\\Program Files (x86)\\TSU\\bin\\TSU.sys");
POBJECT_ATTRIBUTES attr;
InitializeObjectAttributes (attr, &myUnicodeStr, OBJ_OPENIF, NULL, NULL);
NtOpenFile(&file, MAXIMUM_ALLOWED, attr, NULL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_DELETE_ON_CLOSE);
NtClose(file);
As you can see I'm trying to open file with sharing flags set and also used FILE_DELETE_ON_CLOSE since I close the handle and I want it to be deleted afterwards.
The problem I have is Segmentation Fault(I'm using cygwin win10) for some reason. Little debugging showed that problem is in InitializeObjectAttributes function for some reason.
P.S
I know it's not the best solution to delete file when it's handle is held by some other process but the main goal is to mimic the rm.exe's behaviour in this way. Hope you can help. Thanks.
under windows not always possible delete file. for example when some process mapping this file as an image
if try delete running EXE file with rm.exe - it first call ZwOpenFile with DesiredAccess = DELETE, ShareAccess = FILE_SHARE_DELETE and OpenOptions = FILE_OPEN_FOR_BACKUP_INTENT. this is ok. than called ZwSetInformationFile with FileDispositionInformation - the DeleteFile from FILE_DISPOSITION_INFORMATION set to TRUE. this call failed with status STATUS_CANNOT_DELETE.
filesystem return STATUS_CANNOT_DELETE exactly from this place:
// Make sure there is no process mapping this file as an image.
if (!MmFlushImageSection( &Fcb->NonPaged->SectionObjectPointers,
MmFlushForDelete )) {
DebugTrace(-1, Dbg, "Cannot delete user mapped image\n", 0);
return STATUS_CANNOT_DELETE;
}
than rm.exe again try open file, already with OpenOptions = FILE_OPEN_FOR_BACKUP_INTENT | FILE_DELETE_ON_CLOSE options. but this call of course fail with the same STATUS_CANNOT_DELETE. now error from this point:
// If the user wants to delete on close, we must check at this
// point though.
//
if (FlagOn(*DesiredAccess, FILE_WRITE_DATA) || DeleteOnClose) {
Fcb->OpenCount += 1;
DecrementFcbOpenCount = TRUE;
if (!MmFlushImageSection( &Fcb->NonPaged->SectionObjectPointers,
MmFlushForWrite )) {
Iosb.Status = DeleteOnClose ? STATUS_CANNOT_DELETE :
STATUS_SHARING_VIOLATION;
try_return( Iosb );
}
}
after this rm.exe again call ZwSetInformationFile already with FileRenameInformation - where RootDirectory from FILE_RENAME_INFORMATION point to volume (on which file located) root (so like \Device\HarddiskVolume<N>\ and FileName point to some path in recycle bin. as result file actually moved but not deleted. rm.exe deceives you

ShellExecute to open an .exe in 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.

ShellExecute: Verb "runas" does not work for batch files with spaces in path

I am using ShellExecuteW to start a batch file. Code looks somewhat like this:
ShellExecuteW(GetDesktopWindow(), wide_verb.c_str(), wide_filename.c_str(), wide_parameters.c_str(), NULL, SW_SHOW);
Where the wide_ variables are of type wstring. This code works fine for arbitrary combinations of file path and verb, except for verb "runas" (to get administrator rights) and a batch file with spaces in the given path wide_filename. The UAC prompt will pop up and, after confirmation, for a short moment the command prompt will flash up saying that the path could not be found, cutting off at the first space in the path.
I already tried wrapping the file name in additional quotes, but that didn't help.
The reason is probably that .bat files aren't really "executables", but documents, and therefor
ShellExecuteW(GetDesktopWindow(), L"runas", L"C:\\Path To\\file.bat", L"Parameters For Batch", NULL, SW_SHOW);
is internally mapped to an elevated execution of
cmd.exe /C "C:\Path To\file.bat" "Parameters for Batch"
while it should be something along the lines of
cmd.exe /S /C " "C:\Path To\file.bat" Parameters for Batch "
And indeed, if I execute something like this:
ShellExecuteW(GetDesktopWindow(), L"runas", L"cmd.exe", L"/S /C \" \"C:\\Path To\\file.bat\" Parameters For Batch \"", NULL, SW_SHOW);
it does the trick!
However, it doesn't seem like the cleanest approach to "hardcode" the executable (cmd.exe in this case) like that. For batch files it really doesn't matter, but for other script-type documents that take parameters it might become relevant.
So the question is: Is it possible to execute a batch file (with spaces in its path) elevated via ShellExecute or a similar mechanism, without explicitly passing it to cmd.exe?
CreateProcessWithLogonW is what you need here. It has a lot of arguments, but at the end of the page you'll see an Examples section instructing how to initialize the args.
As for hardcoding cmd.exe here's what CreateProcess official doc says (at the end of the section describing the lpApplicationName argument):
To run a batch file, you must start the command interpreter; set lpApplicationName to cmd.exe and set lpCommandLine to the following arguments: /c plus the name of the batch file.
So i think you're good. Of course the path enclosing in "s still applies.

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?

c++ calling ShellExecute without specifying path

i got a file name that is predefined as a string.
can i call shellexcute without specifying the full path? like:
ShellExecute(NULL, L"open", PreDefiendFileName, NULL, NULL, 0);
what should i do otherwise if not?
You could:
Fail gracefully: let the user know that the file could not be found and halt the program.
Use a default file that you *know* is there, and inform the user that you are using the default file.
I'd encourage you to think defensively about this problem. E.g. ensure that the file name that is passed is actually a file ("does it have the correct extension?", "does it exist in the file system?") and then only call the ShellExecute function if those checks pass.