Finding a printer, and then creating a shortcut to that printer


Today's "Little Program" does two things: It looks for a printer in the Printers folder, and then once it finds it, it creates a shortcut to that printer.

As is common with "Little Programs", I don't bother with error checking. I'll leave you to do that.

Second part first, since it is handy on its own: Creating a shortcut to an arbitrary item in the shell namespace, provided either in the form of an ID list or a shell item. (The ID list is the thing that identifies an item in the shell namespace.)

void CreateShortcutToIDList(PCWSTR pszName, PCUIDLIST_ABSOLUTE pidl)
{
 CComPtr<IShellLink> spsl;
 spsl.CoCreateInstance(CLSID_ShellLink);
 spsl->SetIDList(pidl);
 CComQIPtr<IPersistFile>(spsl)->Save(pszName, TRUE);
}

void CreateShortcutToItem(PCWSTR pszName, IShellItem *pitem)
{
 CComHeapPtr<ITEMIDLIST_ABSOLUTE> spidl;
 CComQIPtr<IPersistIDList>(pitem)->GetIDList(&spidl);
 CreateShortcutToIDList(pszName, spidl);
}

Neither of these is particular complicated. To create a shortcut given an ID list:

  • Create a brand new Shell­Link object.
  • Tell that shell link object to point to our desired ID list.
  • Save the shell link.

To create a shortcut given a shell item:

  • Ask the IShell­Item for its ID list.
  • Create a shortcut to that ID list.

Okay, now the first half: Finding the printer. That is a matter of binding to the Printers folder and enumerating its contents. When we find the one whose name matches the target printer, we declare victory.

int __cdecl wmain(int argc, wchar_t **argv)
{
 CCoInitialize init;
 CComPtr<IShellItem> spPrinters;
 SHGetKnownFolderItem(FOLDERID_PrintersFolder, KF_FLAG_DEFAULT,
                      nullptr, IID_PPV_ARGS(&spPrinters));
 CComPtr<IEnumShellItems> spEnum;
 spPrinters->BindToHandler(nullptr, BHID_EnumItems,
                              IID_PPV_ARGS(&spEnum));
 for (CComPtr<IShellItem> spPrinter;
      spEnum->Next(1, &spPrinter, nullptr) == S_OK;
      spPrinter.Release()) {
  CComHeapPtr<wchar_t> spszName;
  spPrinter->GetDisplayName(SIGDN_NORMALDISPLAY, &spszName);
  wprintf(L"Found printer \"%ls\"\n", spszName);
  if (lstrcmpiW(spszName, argv[1]) == 0) {
   wprintf(L"Creating shortcut as \"%ls\"\n", argv[2]);
   CreateShortcutToItem(argv[2], spPrinter);
  }
 }
 return 0;
}

This is a little trickier, but not by much.

  • Initialize COM.
  • Get the IShell­Item for the Printers folder.
  • Get the enumerator for the Printers folder.
  • For each item in the Printers folder:
    • Get its name and print it just for diagnostic purposes.
    • If the name matches the one we're looking for, then create the shortcut.

Here are the header files I used:

#define STRICT_TYPED_ITEMIDS // enable stricter type checking on ITEMIDs
#include <windows.h>
#include <atlbase.h>        // for CComPtr, CComQIPtr
#include <atlalloc.h>       // for CComHeapPtr
#include <shlobj.h>         // for shell interfaces
#include <knownfolders.h>   // for FOLDERID_PrintersFolder
#include <stdio.h>          // for wprintf

For those of you who prefer to work with CSIDLs, the change is relatively minor. Replace the SHGet­Known­Folder­Item call with

 BindToCsidlItem(CSIDL_PRINTERS, &spPrinters);

You may have noticed that I used the for-if anti-pattern. I could've gone for the item directly by using SHCreate­Item­From­Relative­Name:

int __cdecl wmain(int argc, wchar_t **argv)
{
 CCoInitialize init;
 CComPtr<IShellItem> spPrinters;
 SHGetKnownFolderItem(FOLDERID_PrintersFolder, KF_FLAG_DEFAULT,
                      nullptr, IID_PPV_ARGS(&spPrinters));
 CComPtr<IShellItem> spPrinter;
 SHCreateItemFromRelativeName(spPrinters, argv[1], nullptr,
                              IID_PPV_ARGS(&spPrinter));
 CreateShortcutToItem(argv[2], spPrinter);
 return 0;
}

As before, if you're the sort of person who prefers to do things old-school, you can parse the name yourself, at which point you may as well give up on shell items, hike up your pants, and do it with an onion on your belt:

HRESULT BindToIDList(PCUIDLIST_ABSOLUTE pidl,
                     REFIID riid, void **ppv)
{
 *ppv = nullptr;
 CComPtr<IShellFolder> spsfDesktop;
 HRESULT hr = SHGetDesktopFolder(&spsfDesktop);
 if (SUCCEEDED(hr)) {
  if (pidl->mkid.cb) {
   hr = spsfDesktop->BindToObject(pidl, nullptr, riid, ppv);
  } else {
   hr = spsfDesktop->QueryInterface(riid, ppv);
  }
 }
 return hr;
}

int __cdecl wmain(int argc, wchar_t **argv)
{
 CCoInitialize init;
 CComHeapPtr<ITEMIDLIST_ABSOLUTE> spidlPrinters;
 SHGetSpecialFolderLocation(nullptr,
                  CSIDL_PRINTERS, &spidlPrinters);
 CComPtr<IShellFolder> spsfPrinters;
 BindToIDList(spidlPrinters, IID_PPV_ARGS(&spsfPrinters));

 ULONG cchEaten;
 DWORD dwAttributes = 0;
 CComHeapPtr<ITEMIDLIST_RELATIVE> spidl;
 spsfPrinters->ParseDisplayName(nullptr, nullptr, argv[1],
                        &cchEaten, &spidl, &dwAttributes);

 CComHeapPtr<ITEMIDLIST_ABSOLUTE> spidlPrinter;
 spidlPrinter.Attach(ILCombine(spidlPrinters, spidl));
 CreateShortcutToIDList(argv[2], spidlPrinter);
 return 0;
}

The Bind­To­ID­List function is nothing special; we already saw the guts of it when we wrote Bind­To­Csidl­Item.

The main program proceeds in three steps:

  • Get the ID list for the Printers folder and bind to it.
  • Parse the printer name, producing a printer ID list.
  • Create a shortcut to the ID list for the printers folder combined with the printer ID list.
Comments (6)
  1. Love the little program series. Thank you. IShellItem

    Well, what my wife requested: she'd like a taskbar item pinned and used to go directly to the "See What's Printing" window for a particular printer. She frequently needs to manage (ie. pause/cancel) large print jobs and the move to Windows 8 has made finding the print queue harder for her and I get the blame for that move :(

    I tried pinning the printer icon for her but that's not available for some reason. It's not clear why there can be a shortcut on the desktop but not pinned to the taskbar. For now, it's a few steps: go to Desktop, open shortcut and See what's printing

    Perhaps I can use the essentials of binding to the handler in order to dig out the proper context menu item to invoke in a small helper program.   I haven't investigated the possibility of managing the print queue from a Windows Store App — can that be done?

  2. I found that I can create a shortcut to See What's Printing from the printer info panel. That cannot be pinned but is probably good enough for the wife's needs.

  3. This may be some sort of heresy, fourbadcats, but did you try to Pin that shortcut instead to the Start screen? (Should be in the right-click context menu on the shortcut?) The Start screen doesn't appear to have anywhere near the number of restrictions that the Taskbar does.

  4. @fourbadcats. Pinning troubles? Use Taskbar Pinner from Winaero. No restrictions on pinning.

    [I like how somebody wrote an entire app so you don't have to hold the shift key. -Raymond]
  5. Thanks for the suggestions everyone. The shortcuts to the printer itself and to "See What's Printing" are not pinnable even with the Shift+RMB menu. I suspect this is b/c they are really entry points into a control panel DLL.

    She'll live with the desktop icon for now.

  6. "[I like how somebody wrote an entire app so you don't have to hold the shift key. -Raymond]"

    You need to look more closely at what the Taskbar allows for pinning with Shift+right click vs what that app allows.

Comments are closed.