Using ShellExecuteEx to open an executable, also specifying an lpClass? - windows

I have read this and understand lpClass can be used to fix the "wrong file extension issue". However, when I am reading the following lines of code, I can't figure out what lpClass is used for when opening an executable file.
//code excerpt from foo.exe
SHELLEXECUTEINFO info;
ZeroMemory(&info, sizeof(SHELLEXECUTEINFO));
info.cbSize = sizeof(SHELLEXECUTEINFO);
info.nShow = SW_NORMAL;
info.lpVerb = L"open";
info.lpClass = L"ProgId Of foo.exe"; //what is this used for???
info.fMask = SEE_MASK_FLAG_LOG_USAGE | SEE_MASK_CLASSNAME;
info.lpFile = L"bar.exe";
info.lpParameters = lpszParam;
ShellExecuteEx(&info);
Without lpClass being specified, if lpVerb is "open" and lpFile is an exe, running the code simply executes the exe. But what if lpClass is specified as in this case?

The parameter lpClass should be the progID of the file type. What does that mean?
Well consider what happens if you don't pass the class.
In reality, it means the Shell looks up the file extension (e.g. .htm) in the registry, under HKEY_CLASSES_ROOT\.htm. Then it checks the default value which is generally htmlfile. (It also uses other tricks, but in the vast majority of cases it's the extension which determines the progid).
Next it looks up HKEY_CLASSES_ROOT\htmlfile, and uses the information there (under HKEY_CLASSES_ROOT\htmlfile\shell\open) to decide how to open the file.
So how do you use lpClass? Well, for example, suppose you have an .TXT file, but you know it is really html, you can pass "htmlfile" as the lpclass parameter. This will skip the step 1 (looking at the file extension to find the class) and go straight to step 2. This will (usually) cause the file to be opened in a browser instead of notepad.
In your example you have passed "bar.exe" as the lpFile parameter. If you pass "txtfile" as lpClass you should find that instead of running bar.exe it opens it in notepad.

Related

Scripting Word from vbs

I'm trying to get Word to fill in cells in a table. The script works when run as a macro from within Word, but fails when saved as a .vbs file and double-clicked, or run with wscript. This is a part of it.
set obj = GetObject(,"Word.Application)
With obj
With .Selection
MsgBox .text
If (.Information(wdWithInTable) = True) Then
.Collapse Direction:=wdCollapseStart
tCols = .Tables(1).Columns.Count
tRow = .Information(wdStartOfRangeRowNumber)
tCol = .Information(wdStartOfRangeColumnNumber)
For I = 2 To 5
.Tables(1).Cell(tRow, I).Range.Text = "fred" & Str(I)
Next
` now make new row
For I = 1 To tCols - tCol + 1
.MoveRight unit:=wdCell
Next
End If
End With
End With
I have three problems. First, it won't compile unless I comment out the .Collapse and .MoveRight lines. Second, although the MsgBox .text displays the selected text, I get "out of range" errors if I try to access any .Information property.
I'm sure I'm missing something very simple: I usually write software for Macs, and I'd do this using AppleScript. This is my first attempt at getting anything done under Windows.
VBScript and VBA are different languages.
They are a bit similar, but not very. Moreover, VBScript is not like AppleScript; it doesn't let you easily interface with running programs.
The interfaces you'll get from VBScript can behave subtly differently in VBA and VBScript. However, I think you've got two problems here:
:= is invalid syntax in VBScript; you'll need to find an alternative way of calling the function. Try just using positional arguments.
You've no guarantee that this will open the expected file; there could be another instance of Word that it's interacting with instead.
Since your code is not running within the Word environment it would require a reference to the Word object library in order to use enumeration constants (those things that start with wd).
VBScript, however, cannot work with references, which means the only possibility is to use the long value equivalents of the enumerations. You'll find these in the Word Language References. Simplest to use is probably the Object Browser in Word's VBA Editor. (In Word: Alt+F11 to open the VBA Editor; F2 to start the Object Browser; type in the term in the "Search" box, click on the term, then look in the bottom bar.)
The code in the question uses, for example:
wdWithInTable
wdCollapseStart
wdStartOfRangeRowNumber
wdStartOfRangeColumnNumber
wdCell
The reason you get various kinds of errors depends on where these are used.
Also, VBScript can't used named parameters such as Unit:=. Any parameters must be passed in comma-delimited format, if there's more than one, in the order specified by the method or property. If there are optional parameters you don't want to use these should be left "blank":
MethodName parameter, parameter, , , parameter

Get the file type of a file using the Windows API

I am trying to identify when a file is PNG or JPG to apply it as a wallpaper. I am using the SHGetFileInfo to get the type name with the .szTypeName variable, but I just realized that it changes if the OS is in another language.
This is my code:
SHFILEINFOW fileInfo;
UINT sizeFile = sizeof(fileInfo);
UINT Flags = SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES;
// Getting file info to find out if it has JPG or PNG format
SHGetFileInfoW(argv[1], 0, &fileInfo, sizeFile, Flags);
This is how I am validating:
if (wcscmp(fileInfo.szTypeName, L"JPG File") == 0)
{
//Code here
}
When the OS is in spanish, the value changes to "Archivo JPG" so I would have to validate against all language, and does not make sense.
Any idea what other function I can use?
This API is meant to be used to produce a user-facing string representation for known file types1). It is not meant to be used to implement code logic.
More importantly, it doesn't try to parse the file contents. It works off of the file extension alone. If you rename an Excel workbook MySpreadsheet.xlsx to MySpreadsheet.png, it will happily report, that this is a "PNG File".
The solution to your problem is simple: You don't have to do anything, other than filtering on the file extension. Use PathFindExtension (or PathCchFindExtension for Windows 8 and above) to get the file extension from a fully qualified path name.
This can fail, in case the user appended the wrong file extension. Arguably, this isn't something your application should fix, though.
As an aside, you pass SHGFI_USEFILEATTRIBUTES to SHGetFileInfoW but decided to not pass any file attributes (second argument) to the call. This is a bug. See What does SHGFI_USEFILEATTRIBUTES mean? for details.
1) It is the moral equivalent of SHGFI_DISPLAYNAME. The only thing you can do with display names is display them.

ShellExecuteEx silently fails in 64-bit call to hidden file

I have a 64-bit console app that just wants to open a file. File's hidden attribute is set (the file is hidden). The code below fails only on some machines. ShellExecuteEx would actually return TRUE, but the .txt file would not open in Notepad, and hProcess member of SHELLEXECUTEINFO structure remains zero after the call.
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
SHELLEXECUTEINFO sei = {0};
sei.cbSize = sizeof(SHELLEXECUTEINFO);
sei.fMask = SEE_MASK_NOCLOSEPROCESS;
sei.hWnd = GetConsoleWindow();
sei.lpVerb = _T("open");
sei.nShow = SW_SHOWNORMAL;
sei.lpFile = _T("C:\\some\\existing\\file.txt");
BOOL bRC = ShellExecuteEx(&sei);
MessageBox(GetConsoleWindow(), sei.lpFile, sei.lpVerb, MB_OK);
Call to MessageBox is there just so that there's enough time for ShellExecuteEx to do its magic. The .txt file will open in Notepad if at least one of the following conditions is met:
.txt file is not hidden
the calling process is 32-bit, instead of 64-bit
the machine is some other (can't figure out what is the difference between machines, but the file is not open in at least one Vista and one Windows 8.1)
lpVerb is nullptr, "openas" or "properties" (which will of course just show file's properties), instead of "open", "edit", or even "runas" (for .exe instead of .txt)
What is going on here? Windows Explorer properly opens the hidden file, because it uses null verb, but I have to use the verb ("runas" actually, but please don't be distracted by this info). It should work with "open" as well. What am I doing wrong?
Turns out the problem was in one Explorer extension. Ironically (but unsurprisingly) it was our own extension. If the extension is unregistered the problem is gone, and ShellExecute works again for all verbs.

Can I get the return value of Shell.Application's ShellExecute?

I'm using VBScript to create a UAC prompt for a batch file. I don't know how to get the return value of the UAC prompt. For example if I try to UAC a file that doesn't exist I should get an error, right?
For example:
Dim rc
Set UAC = CreateObject("Shell.Application")
rc = UAC.ShellExecute("thisdoesntexist.exe", "", "", "runas", 1)
WScript.Echo rc
rc doesn't contain a code. Also, is there any way I can get the error code of whatever I'm executing? Is ShellExecute asynchronous in VBScript?
IShellDispatch2.ShellExecute method
Performs a specified operation on a specified file.
Syntax
IShellDispatch2.ShellExecute(sFile [, vArguments] [, vDirectory] [,
vOperation] [, vShow]) Parameters
sFile Required. String that contains the name of the file on which
ShellExecute will perform the action specified by vOperation.
vArguments Optional. Variant that contains the parameter values for
the operation.
vDirectory Optional. Variant that contains the fully qualified path of
the directory that contains the file specified by sFile. If this
parameter is not specified, the current working directory is used.
vOperation Optional. Variant that specifies the operation to be
performed. It should be set to one of the verb strings that is
supported by the file. For a discussion of verbs, see the Remarks
section. If this parameter is not specified, the default operation is
performed.
vShow Optional. Variant that recommends how the window that belongs to
the application that performs the operation should be displayed
initially. The application can ignore this recommendation. vShow can
take one of the following values. If this parameter is not specified,
the application uses its default value.0
Open the application with a hidden window.
1 Open the application with a normal window. If the window is
minimized or maximized, the system restores it to its original size
and position.
2 Open the application with a minimized window.
3 Open the application with a maximized window.
4 Open the application with its window at its most recent size and
position. The active window remains active.
5 Open the application with its window at its current size and
position.
7 Open the application with a minimized window. The active window
remains active.
10 Open the application with its window in the default state specified
by the application.
Return Value
No return value.
Remarks
This method is equivalent to launching one of the commands associated
with a file's shortcut menu. Each command is identified by a verb
string. The supported verbs vary from file to file. The most commonly
supported verb is "open", which is also usually the default verb.
Others might be supported only by certain types of files. For further
discussion of Shell verbs, see Launching Applications or Extending
Shortcut Menus.
This method is not currently available in Microsoft Visual Basic.
Examples
The following example uses ShellExecute to open Microsoft Notepad.
Proper usage is shown for Microsoft JScript and Visual Basic Scripting
Edition (VBScript).
<script language="VBScript">
function fnShellExecuteVB()
dim objShell
set objShell = CreateObject("Shell.Application")
objShell.ShellExecute "notepad.exe", "", "", "open", 1
set objShell = nothing
end function
</script>
Now all COM calls look like this HResult = methodcall(param1, param2, ..., paramn, OUTPARAM).
VB pretends it OUTPARAM = methodcall(param1, Param2, ..., paramn) with HResult appearing in the err.object.
So errors will still fire, it's just that it doesn't wait to find out.

SHFileOperation FOF_ALLOWUNDO fails on long filenames

I'm using the following function to delete a file to the recycle bin: (C++, MFC, Unicode)
bool DeleteFileToPaperbasket (CString filename)
{
TCHAR Buffer[2048+4];
_tcsncpy_s (Buffer, 2048+4, filename, 2048);
Buffer[_tcslen(Buffer)+1]=0; //Double-Null-Termination
SHFILEOPSTRUCT s;
s.hwnd = NULL;
s.wFunc = FO_DELETE;
s.pFrom = Buffer;
s.pTo = NULL;
s.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOERRORUI;
s.fAnyOperationsAborted = false;
s.hNameMappings = NULL;
s.lpszProgressTitle = NULL;
int rc = SHFileOperation(&s);
return (rc==0);
}
This works nicely for most files. But if path+filename exceed 255 characters (and still much shorter that 2048 characters), SHFileOperation returns 124. Which is DE_INVALIDFILES.
But what's wrong? I checked everything a million times. The path is double-null terminated, I'm not using \\?\ and it works for short filenames.
I'm totally out of ideas...
I think backwards comparability is biting you in the --- in several ways, and I'd need to actually see the paths your using and implement some error checking code to help. But here are some hints.
You would not get a DE_INVALIDFILES 0x7C "The path in the source or destination or both was invalid." for a max path violation, you'd get a DE_PATHTOODEEP 0x79 "The source or destination path exceeded or would exceed MAX_PATH."
These error codes(return value) do, can, and have changed over time, to be sure what your specific error code means, you need to check it with GetLastError function(msdn)
Also, taken from the SHFileOperation function documentation: "If you do not check fAnyOperationsAborted as well as the return value, you cannot know that the function accomplished the full task you asked of it and you might proceed under incorrect assumptions."
You should not be using this API for extremely long path names, it has been replaced in vista+ by IFileOperation interface
The explanation for why it may work in explorer and not thru this LEGACY api is - Taken from the msdn page on Naming Files, Paths, and Namespaces
The shell and the file system have different requirements. It is
possible to create a path with the Windows API that the shell user
interface is not able to interpret properly.
Hope this was helpful
The recycle bin doesn't support files whose paths exceed MAX_PATH in length. You can verify this for yourself by trying to recycle such a file in Explorer - you will get an error message about the path being too long.

Resources