Getting File Associations using Windows API - c++

I'm working on a console based file browser for Windows in C++ and am having difficulties getting together a context menu that lists actions associated with a file and calls commands on them. The biggest issue right now is getting the actions tied to the file types.
I know of the process to open and tweak the registry keys in HKEY_CLASSES_ROOT but I can't find a way to actually get the actions and their commands so I can build a context menu out of it.
The general structure of these associations in the registry is:
HKEY_CLASSES_ROOT\(extension)\(default) - filetype
HKEY_CLASSES_ROOT\filetype\(default) - description of the filetype
HKEY_CLASSES_ROOT\filetype\shell\action\(default) - description of the action
HKEY_CLASSES_ROOT\filetype\shell\action\command\(default) - command called on file
I'm wondering if there is a way (hopefully using the Windows API) that I can get all of the actions associated with a file type. At least then I can check those actions for their commands in the registry...
Also, this approach doesn't seem to work with some common file types (e.g. mp3) on my system as the default key is left blank and another key ("PercievedType") is set to audio... How can I get the actions for something like this?
Lastly, if there is a better way to do this in general I would love to hear it, I generally hate dealing with the registry. I would much rather have a simple windows call that would get me the actions and commands...

Try this (error handling omitted for brevity):
TCHAR szBuf[1000];
DWORD cbBufSize = sizeof(szBuf);
HRESULT hr = AssocQueryString(0, ASSOCSTR_FRIENDLYAPPNAME,
argv[1], NULL, szBuf, &cbBufSize);
if (FAILED(hr)) { /* handle error */ }
CStringA strFriendlyProgramName(szBuf, cbBufSize);
cbBufSize = sizeof(szBuf);
hr = AssocQueryString(0, ASSOCSTR_EXECUTABLE,
argv[1], NULL, szBuf, &cbBufSize);
if (FAILED(hr)) { /* handle error */ }
CStringA strExe(szBuf, cbBufSize);
std::cout << strFriendlyProgramName << " (" << strExe << ")" << std::endl;

Consider using IContextMenu. IContextMenu is how Windows Explorer accesses the context menu for files and items.
This article by Raymond Chen has sample code for how to access IContextMenu for a given file path and use it to fill an HMENU with the set of available commands. It's the first of a series of articles that give a decent overview along with sample code.

Related

C++ Printing using the printer API

I have an inquiry in the process of printing the printer.
Below are three of my program descriptions.
[My Application info]
Hook the APIs related to printing, such as StartDoc, StartPage, ExtTextOut, EndPage, and EndDoc.
After checking the entire string obtained from ExtTextOut in EndDoc API during printing, when a specific string is detected, the printer is deleted and the approval page is viewed.
After receiving approval, I would like to reprint the existing printer.
(JOB_CONTROL_RESUME will not be used in my program. You must print again after deleting unconditionally.)
Don't show the dialog again when reprinting.
With all the property information of the print that was started at the beginning in the DocumentProperties API, the same print is restarted with that information.
[Current Re-Print Logic]
This is my current status.
If you delete Print and proceed with printing again (this is the logic to run StartDoc, StartPage, Endpage, EndDoc that was previously in progress after creating a new printer DC), in a specific document (textbox of Excel, etc.), normal output is not possible.
I checked MSDN and it was confirmed that there is no API to output a specific print name in Windows, so I wrote the code as above.
[PowerShell Command]
Also, for printing, I tried the following command using PowerShell to print several files (ppt, xls, doc ...etc) including txt files.
PowerShell > Start-Process -FilePath "C:\Users\DeveloperO\Desktop\1234.docx" -Verb print
The above command worked, but the result is not what I want because a new process has been started.
Starting a new process and starting a print doesn't work for me.
[Please Answer]
Question 0)
Is there a command to print an open process without starting the process using powershell?
(Currently, I know that only txt files can be printed.)
Question 1)
I've been working in C++ language, is there an API to programmatically request a printer using C++?
Question 2)
I've also tried the ScheduleJob API. But this keeps giving 3002 Error. (Cannot find spool file.)
The code for this is below, can you give me some advice?
int main()
{
BOOL bResult = FALSE;
HANDLE hPrinter = NULL;
DWORD dNeedNum = 0;
CHAR szFullPath[MAX_PATH] = { 0, };
DWORD size = 4096;
ADDJOB_INFO_1 * JobInfo = { 0, };
W2M(g_wszFullPath, szFullPath);
bResult = OpenPrinter("Printer Name", &hPrinter, NULL);
if (bResult == FALSE)
{
printf("Error = %d", GetLastError());
}
JobInfo = (ADDJOB_INFO_1)LocalAlloc((LMEM_FIXED / LMEM_ZEROINIT), size);
bResult = AddJob(hPrinter, 1, (BYTE)JobInfo, size, &dNeedNum);
if (bResult == FALSE)
{
printf("Error = %d", GetLastError());
}
ZeroMemory(JobInfo->Path, sizeof(JobInfo->Path));
StringCbCopy(JobInfo->Path, sizeof(szFullPath), szFullPath);
bResult = ScheduleJob(hPrinter, JobInfo->JobId);
if (bResult == FALSE)
{
printf("Error = %d", GetLastError()); // <- Error Point 3002 Error
}
LocalFree(JobInfo);
ClosePrinter(hPrinter);
}
This isn't an answer to your specific questions 1,2 and 3, but in your comment, you mention 'What I want is to print and existing file or document'.
In this case, the Win32 APIs will only print GDI or XPS documents*, which means if you have a word document you want to print, you'll need to either convert it to XPS first, or use Word interop to print it, where the Word application will convert it to XPS or GDI and print it.
Unfortunately, any simple Win32 APIs for printing XPS files have been deprecated and are noted to be removed in later versions of windows.
Therefore, the remaining root to printing an XPS document is using the Print Document API, which is unfortunately non-trivial to explain.
https://learn.microsoft.com/en-us/windows/win32/printdocs/printdocs-printing
The premise at a high level would be to:
Open your document to print as an xps package using CreatePackageFromStream(...)
Create a package target for your print job using CreateDocumentPackageTargetForPrintJob(...).
Get a package target and package writer for printing your document using GetPackageTarget(...) and GetXpsOMPackageWriter(...).
Enumerate the documents and pages in the xps package and print them to your package writer.
The steps above don't really do justice to the complexity of these APIs I'm afraid.
If you need to pass in settings, you'll need to:
Get the default devmode for the printer you're targeting.
Convert the devmode to a print ticket for that printer.
Update the settings in the print ticket.
Use the print ticket in the relevant package writer APIs.
As an alternative, if you can use a separate printing process, you could use the C# APIs which are a lot more simple, and take a path to a file and print it with a single call.
https://learn.microsoft.com/en-us/dotnet/api/system.printing.printqueue.addjob?redirectedfrom=MSDN&view=net-5.0#overloads
* Unless your using passthrough APIs, with a specific printer driver.

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.

How to find if an document can be OPENed via ShellExecute?

I want to check if a particular file can be successfully "OPEN"ed via ShellExecute, so I'm attempting to use AssocQueryString to discover this.
Example:
DWORD size = 1024;
TCHAR buff[1024]; // fixed size as dirty hack for testing
int err = AssocQueryString(0, ASSOCSTR_EXECUTABLE, ".mxf", NULL ,buff , &size);
openAction->Enabled = ((err == S_OK) || (err == S_FALSE)) && (size > 0);
Now, this almost works. If there's a registered application, I get the string.
But, there's a catch: On Vista, even if there is no registered application, It returns that the app c:\Windows\System32\shell32.dll is associated, which is the thing that brings up the 100% useless "Windows cannot open this file: Use the Web service to find the correct program?" dialog.
Obviously I want to hide that peice of cr*p from end users, but simply comparing the returned string to a constant seems like an ugly, brute-force and fragile way of doing it.
Also, hacking the registry to totally disable this dialog isn't a great idea.
What's a better option?
I always use FindExecutable() to get the registered application for a given document.
There is another way to do this, using the ASSOCF_INIT_IGNOREUNKNOWN option flag with AssocQueryString().
int err = AssocQueryString(ASSOCF_INIT_IGNOREUNKNOWN, ASSOCSTR_EXECUTABLE, ".mxf", NULL ,buff , &size);
This has a couple of important advantages over using FindExecutable()
It can work with just the file extension, while FindExecutable needs a full path to an existing file of the specified type.
Because it's not accessing the file, it's much faster with Samba and other network storage. Calling FindExecutable() on one file in a directory containing ~3000 files via Samba took > 1 second in my tests.

How can I start explorer.exe via C++?

I'm trying to programmatically start explorer.exe but I'm not having any luck.
This is my code:
cout << pName << "died, lets restart it." << endl;
STARTUPINFO startupInfo = {0};
startupInfo.cb = sizeof(startupInfo);
PROCESS_INFORMATION processInformation;
if(CreateProcess(pName, NULL, NULL, NULL, false, NORMAL_PRIORITY_CLASS, NULL, NULL, &startupInfo, &processInformation) == 0){
cout << "Error starting " << pName << ": " << GetLastError() << endl;
}
and pName is explorer.exe
Can someone tell me what I'm doing wrong? I get the error code '2' which is ERROR_FILE_NOT_FOUND
The first parameter is the application name; the second is the command line. Try specifying "explorer.exe" as the second parameter.
See this MSDN article:
lpApplicationName [in, optional]
The name of the module to be executed.
This module can be a Windows-based
application. It can be some other type
of module (for example, MS-DOS or
OS/2) if the appropriate subsystem is
available on the local computer.
The string can specify the full path
and file name of the module to execute
or it can specify a partial name. In
the case of a partial name, the
function uses the current drive and
current directory to complete the
specification. The function will not
use the search path. This parameter
must include the file name extension;
no default extension is assumed.
You probably should give "ShellExecuteEx" a try. This function lets you specify a file or folder and a verb that describes what to do with it. If you use "explore" as the verb, it will open Windows Explorer with the given folder.
It's surprisingly hard to find relevant information on how to reliably restart windows explorer. On 64-bit Windows 7/8, the ShellExecute method does not work properly and leads to things such as file copying and icon overlays being completely broken.
The most reliable way seems to use stdlib.h system call:
system("start explorer");
If you are trying to shutdown and restart explorer, you might want to programmatically disable AutoRestartShell registry key, which prevents you from controlling when explorer is restarted.

How to find out the distinguished name of the information store to feed to IExchangeManageStore::GetMailboxTable?

There is a Microsoft knowledge base article with sample code to open all mailboxes in a given information store. It works so far (requires a bit of copy & pasting on compilers newer than VC++ 6.0).
At one point it calls IExchangeManageStore::GetMailboxTable with the distinguished name of the information store. For the Exchange 2007 Trial Virtual Server image it has to look like this:
"/o=Litware Inc/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Configuration/cn=servers/cn=DC1".
Using OutlookSpy and clicking on IMsgStore and IExchangeManageStore reveals the desired string next to "Server DN:".
I want to avoid forcing the user to put this into a config file. So if OutlookSpy can do it, how can my application find out the distinguished name of the information store where the currently open mailbox is on?
Thinking there must be a pure MAPI solution, I believe I've figured out how OutlookSpy does it.
The following code snippet, inserted after
printf("Created MAPI session\n");
in the example from KB194627, will show the Server DN.
LPPROFSECT lpProfSect;
hr = lpSess->OpenProfileSection((LPMAPIUID)pbGlobalProfileSectionGuid, NULL, 0, &lpProfSect);
if(SUCCEEDED(hr))
{
LPSPropValue lpPropValue;
hr = HrGetOneProp(lpProfSect, PR_PROFILE_HOME_SERVER_DN, &lpPropValue);
if(SUCCEEDED(hr))
{
printf("Server DN: %s\n", lpPropValue->Value.lpszA);
MAPIFreeBuffer(lpPropValue);
}
lpProfSect->Release();
}
Update:
There is the function HrGetServerDN in the EDK 5.5 source code, it extracts the Server DN from a given session's PR_EMS_AB_HOME_MTA. I'll try it if the other way turns out to be unreliable.
It'll be in Active Directory, so you'd use ADSI/LDAP to look at CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=example,DC=com. Use Sysinternals' ADExplorer to have a dig around in there to find the value you're looking for.
I'd download the source for MFCMapi and see how they do this.