C++ Printing using the printer API - c++

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.

Related

PDF printer generates PDF only when output file name is not set

The following code snippet sends PostScript content (saved in pBuf buffer) to a CutePDF printer:
if (OpenPrinter(printerName, &hPrinter, NULL))
{
DOC_INFO_1 di1;
di1.pDatatype = L"RAW";
di1.pDocName = L"Raw print document";
di1.pOutputFile = NULL;
StartDocPrinter(hPrinter, 1, (LPBYTE)&di1);
StartPagePrinter(hPrinter);
DWORD dwWritten = 0;
WritePrinter(hPrinter, pBuf, dwBufSize, &dwWritten);
EndPagePrinter(hPrinter);
EndDocPrinter(hPrinter);
}
During the execution of this code, a dialog appears where I specify the name of the output file (e.g. D:/out.pdf), after that the pdf file is generated. So far so good. The problems begin when I'm trying to avoid the filename specifying step by changing Line 4 of the snippet:
di1.pOutputFile = L"D:/out.pdf";
Such code doesn't show the dialog during its execution (as expected), but the result D:/out.pdf isn't a pdf file, it's a copy of the PostScript file sent to the printer (copy of the contents of pBuf buffer). PDF Writer behaves in the same way. Why do PDF printers behave in this way and how can I achieve the needed behaviour (generate PDF file without specifying its name in UI)?
The Windows print system behaves this way, because, to be blunt, that's how its supposed to behave. If you specify a filename at that point then the print system sends the output to that file. If you don't specify a filename then it proceeds to normal processing.
Normally you would send the printer driver output to a port, and in the case of PDF printers a custom port monitor would pick up the output (PostScript in this case) and process it further. For PDF printers they send the PostScript on to a process which converts the PostScript to PDF (almost always using Ghostscript, though the Adobe print to PDF tools work the same way).
If you want to alter the output of the PDF process (ie write it to a different file), then you need to alter the way the port monitor works, not the way the print subsystem works, which is what your code is currently doing. By setting a filename where you are, you are simply short-circuiting the process, never invoking the port monitor, which is why the 'save file' dialog does not appear, and why the output is PostScript.
There may be a way of specifying the output file documented for the specific PDF printer you are using. If not, then for open source products (and if GS is built in they should be GPL licensed) you can request a copy of the source code for the product and alter it to suit yourself.
Alternatively, you can pick up a copy of Ghostscript and RedMon (open source Port Monitor) and create your own tool for doing the same job.

Close shared files programmatically

The company I'm working with has a program written in ye olde vb6, which is updated pretty frequently, and most clients run the executable from a mapped network drive. This actually has surprisingly few issues, the biggest of which is automatic updates. Currently the updater program (written in c++) renames the existing exe, then downloads and places the new version into the old version's place. This generally works fine, but in some environments it simply fails.
The solution is running this command from microsoft:
for /f "skip=4 tokens=1" %a in ('net files') do net files %a /close
This command closes all network files that are shared (well... most) and then the updater can replace the exe.
In C++ I can use the System(""); function to run that command, or I could redirect the output of net files, and iterate through the results looking for the particular file in question and run net file /close command to close them. But it would be much much nicer if there were winapi functions that have similar capabilities for better reliability and future safety.
Is there any way for me to programmatically find all network shared files and close relevant ones?
You can programmatically do what net file /close does. Just include lmshare.h and link to Netapi32.dll. You have two functions to use: NetFileEnum to enumerate all open network files (on a given computer) and NetFileClose to close them.
Quick (it assumes program is running on same server and there are not too many open connections, see last paragraph) and dirty (no error checking) example:
FILE_INFO_2* pFiles = NULL;
DWORD nRead = 0, nTotal = 0;
NetFileEnum(
NULL, // servername, NULL means localhost
"c:\\directory\\path", // basepath, directory where VB6 program is
NULL, // username, searches for all users
2, // level, we just need resource ID
(LPBYTE*)&pFiles, // bufptr, need to use a double pointer to get the buffer
MAX_PREFERRED_LENGTH, // prefmaxlen, collect as much as possible
&nRead, // entriesread, number of entries stored in pFiles
&nTotal, // totalentries, ignore this
NULL //resume_handle, ignore this
);
for (int i=0; i < nRead; ++i)
NetFileClose(NULL, pFiles[i].fi2_id);
NetApiBufferFree(pFiles);
Refer to MSDN for details about NetFileEnum and NetFileClose. Note that NetFileEnum may return ERROR_MORE_DATA if more data is available.

Getting File Associations using Windows API

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.

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 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.