Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Creating a shortcut programmatically in C++

4.89/5 (15 votes)
19 Jul 2013CPOL8 min read 69.6K   2.2K  
This illustrate how to create shortcut to objects (both file and non-file like printers, folders, files. Drives, etc) in Win32 C++

1.0 Introduction 

A shortcut or, in a programmer's jargon, a shell link is a very important object in the PC world. It is used extensively for easy access to “referenced object” (object like file, folder, drive, printer, etc. which the shortcut is pointing to) from anywhere within the local system or network. Installers also use it to reference programs they installed from the desktop. Please note, though several people have reported success in creating a shortcut that points to a URL (Internet Shortcut) using the techniques discussed in this article, however, this is not the ideal way to create an Internet Shortcut. To see how to make an Internet Shortcut, read my article (tip) "A shortcut for your website".

This article will show how to create a shortcut

  1. To a file base object (local and network files, folders, drives, etc.)
  2. To a non-file base object (printer as a case study)

2.0 Prerequisite

An insight into Win32, OLE COM programming and C++ is essential

3.0 Creating the shortcut

Shortcuts are simply binary files with .lnk extension which contains the location (path) to a referenced object and some other information needed to access and or describe the object.

Shortcut creation is achieved in Win32 by the use of OLE COM IShellLink interface to create the shortcut and the IPersistFile interface to save it to disk (Persistent Storage).

3.1 Using the code sample

Grab the source code attached to this article. These contains two files, a header file CreateShortCut.h and a source file “CreateShortCut.cpp”. Add the two to your project and #include the CreateShortCut.h to your project 

C++
#include "CreateShortCut.h"

3.1.1 File base

To create shortcut to a file base object 

C++
CreateShortCut csc;

//Using with the newly introduced CSC_* constants - this example will create a shortcut on the current machine's desktop
ih.CreateLinkFileBase("C:\\somedirectory\\afile.exe", CSC_DESKTOP, "The Comment", MAKEWORD(0x41, HOTKEYF_ALT + HOTKEYF_CONTROL), "-help");

//To force the creation of the destination directory if it doesn't exist
ih.CreateLinkFileBase("C:\\somedirectory\\afile.exe", "C:\\a-non-existing-directory\\", "The Comment", MAKEWORD(0x41, HOTKEYF_ALT + HOTKEYF_CONTROL), "-help", TRUE);

//For the Wide Character version
csc.CreateLinkFileBase(L"C:\\somedirectory\\afile.exe",
L"C:/Users/somedirectory/Desktop", L"The comment to this file",
MAKEWORD(0x41, HOTKEYF_ALT + HOTKEYF_CONTROL), L"-help");

//For the ANSI version
csc.CreateLinkFileBase("C:\\somedirectory\\afile.exe",
"C:/Users/somedirectory/Desktop", "The comment to this file",
MAKEWORD(0x41, HOTKEYF_ALT + HOTKEYF_CONTROL), "-help");

With this a shortcut named afile.lnk which points to C:\\somedirectory\\afile.exe will be created within the directory pointed to by the pathToLink parameter which has “The comment to this file” has it comment and Ctrl + Alt + A has it shortcut key.

The prototype for CreateLinkFileBase is ::CreateLinkFileBase(LPCWSTR pathToObj, LPCWSTR pathToLink, LPCWSTR description, WORD hotkey, LPCWSTR cmdLine, BOOL forceCreate = FALSE); for the Wide Character Version and ::CreateLinkFileBase(LPCSTR pathToObj, LPCSTR pathToLink, LPCSTR description, WORD hotkey, LPCSTR cmdLine, BOOL forceCreate = FALSE) for the ANSI version

  • pathToObj: a null terminated string (wide or ANSI) that contains the path to the referenced object. It must be an absolute path. This could be a path to a file e.g "C:/somedirectory/afile.exe", a directory e.g "C:/somedirectory/", a drive e.g., "c:/", a network file e.g., "\\\\computername\\directory\\file.txt", a network folder e.g., "\\\\computername\\directory\\". Important: The path to a network folder must always end with a double backlash ”\\\\computername\\directory\\”.  
  • pathToLink: a null terminated string (wide or ANSI) or the CSC_* constant that contains the path to the directory the shortcut will be saved to. The ending director separator can be omitted i.e. "c:/directory" and "c:/directory/" are both correct.

    The CSC_* constants include:
    CSC_DESKTOP - path to the file system directory used to physically store file objects on the desktop
    CSC_ALL_DESKTOP - path to the file system directory that contains files and folders that appear on the desktop for all users
    CSC_PROGRAMFILES - path to the Program Files folder
    CSC_PROGRAMFILESX86 - path to the Program Files(x86) folder (on a 64bit system)
    CSC_STARTUP - path to the file system directory that corresponds to the user's Startup program group
    CSC_SENDTO - path to the file system directory that contains Send To menu items
    CSC_WINDOWS - path to the Windows directory
    CSC_SYSTEM32 - path to the System32 directory
    CSC_STARTMENU - path to the file system directory that contains Start menu items

  • description: a null terminated string (wide or ANSI) that contains the description of the referenced object. This text appears in the comment of the link file and the tooltip when the user mouseover the shortcut. This can be NULL.
  • hotkey: this is a WORD. It represents the key combination to activate the shortcut. For more on how to MAKEWORD for hotkey, HOTKEYF_ALT is for the ALT key, HOTKEYF_CONTROL is for the CTRL key and HOTKEYF_SHIFT for the SHIFT key, 0x41 is A, 0x42 is B, ... This can be NULL.
  • cmdLine: a null terminated string (wide or ANSI) that contains the command line arguments to be inputted to the referenced object e.g., "-help". This can be NULL.
  • forceCreate: this is an optional parameter which default to FALSE. If set to TRUE, the directory entered into pathToLink will be created if it does not exist. 

3.1.2 Non-File Base

Shortcut can also be created for non-file base object. The installed printers are chosen as a case study. To create a shortcut to the printer named "Fax"

C++
CreateShortCut csc;

//Using with the newly introduced CSC_* constants - this example will create a shortcut on the current machine's desktop
ih.CreateLinkFileBase("fax", CSC_DESKTOP, "The Printer named Fax", MAKEWORD(0x43, HOTKEYF_ALT + HOTKEYF_CONTROL));

//To force the creation of the destination directory if it doesn't exist
ih.CreateLinkFileBase("fax", "
C:\\a-non-existing-directory\\
", "The Printer named Fax", MAKEWORD(0x43, HOTKEYF_ALT + HOTKEYF_CONTROL), TRUE);


//For Wide Character Version
csc.CreateLinkToPrinter(L"fax",
L"C:/somedirectory/", L"", MAKEWORD(0x43, HOTKEYF_ALT + HOTKEYF_CONTROL));

//For ANSI version
csc.CreateLinkToPrinter("fax", "C:/somedirectory/", 
    "", MAKEWORD(0x43, HOTKEYF_ALT + HOTKEYF_CONTROL));

The prototype for the CreateLinkToPrinter method is ::CreateLinkToPrinter(LPCWSTR printerName, LPCWSTR pathToLink, LPCWSTR description, WORD hotkey, BOOL forceCreate = FALSE); and ::CreateLinkToPrinter(LPCSTR printerName, LPCSTR pathToLink, LPCSTR description, WORD hotkey, BOOL forceCreate = FALSE); for ANSI version

  • printerName: a null terminated string (wide or ANSI) that contains the name of the installed printer. This is case insensitive so "Fax" and "fax" are the same.
  • pathToLink: a null terminated string (wide or ANSI) or the CSC_* constant that contains the path to the directory the shortcut will be saved to. The ending director separator can be omitted i.e. "c:/directory" and "c:/directory/" are both correct.

    The CSC_* constants include:
    CSC_DESKTOP - path to the file system directory used to physically store file objects on the desktop
    CSC_ALL_DESKTOP - path to the file system directory that contains files and folders that appear on the desktop for all users
    CSC_PROGRAMFILES - path to the Program Files folder
    CSC_PROGRAMFILESX86 - path to the Program Files(x86) folder (on a 64bit system)
    CSC_STARTUP - path to the file system directory that corresponds to the user's Startup program group
    CSC_SENDTO - path to the file system directory that contains Send To menu items
    CSC_WINDOWS - path to the Windows directory
    CSC_SYSTEM32 - path to the System32 directory
    CSC_STARTMENU - path to the file system directory that contains Start menu items.

  • description: a null terminated string (wide or ANSI) that contains the description of the referenced object. This text appears in the comment of the link file and the tooltip when the user mouseover the shortcut. This can be NULL.
  • hotkey: this is a WORD. It represents the key combination to activate the shortcut. For more on how to MAKEWORD for hotkey, HOTKEYF_ALT is for the ALT key, HOTKEYF_CONTROL is for the CTRL key and HOTKEYF_SHIFT for the SHIFT key, 0x41 is A, 0x42 is B,... This can be NULL.
  • forceCreate: this is an optional parameter which default to FALSE. If set to TRUE, the directory entered into pathToLink will be created if it does not exist.  

4.0 Code Explanation

For those that wants to know how the whole class works

4.1 File base object

The file base object shortcut creation is handled by the overloaded ::CreateLinkFileBase method. The ANSI version simply convert it parameters into Wide Characters and call the Wide Character version with them.  The concept is to firstly used the _wstat() function to verify if the object pathToObj is a folder or a (file or drive) and CoCreateInstance() is called based on the result.

According to MSDN documentation, CoCreateInstance() should be called with CLSID_FolderShortCut as the first parameter if the reference object is a folder but the result was not satisfactory to me. The shortcut was created alright but

  1. Windows Explorer shows the .lnk extension even when all extension is set to hidden
  2. When the shortcut is double clicked, it opened the folder it points to quite alright but the address bar in Windows explorer points to the shortcut location instead of the folder location

Leaving the first parameter of CoCreateInstance as CLSID_ShellLink against MSDN advice solved the two problems.

C++
.
.
.
if(isFile)
hRes = CoCreateInstance(CLSID_ShellLink, NULL,
CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
else
{
//hRes = CoCreateInstance(CLSID_FolderShortcut, NULL,
//       CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
hRes = CoCreateInstance(CLSID_ShellLink, NULL, 
          CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
}
.
.
.

A IShellLink pointer is created and values are set accordingly and object queried for IPersistFile interface that is used to save the shortcut to persistence storage.

C++
BOOL isFile = FALSE;
//Find out if it is a file or a folder
struct _stat s;
if(_wstat(pathToObj, &s) == 0)
{
	if( s.st_mode & S_IFDIR )
		isFile = FALSE;
	else if( s.st_mode & S_IFREG )
		isFile = TRUE;
	else
	{
		ReportError("Not file, device, or folder");
		return 0;
	}
}
else
{
	ReportError("Path not found");
	return 0;
}

//Appending '/' to pathToLink if not present
memset(path, '\0', MAX_PATH);
_wmakepath_s(path, MAX_PATH, NULL, pathToLink, NULL, NULL);


//***create the directory
if(forceCreate)
{
	createDir(pathToLink);
}
	
HRESULT hRes;
IShellLink* psl;

if(isFile)
	hRes = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
else
{
	//hRes = CoCreateInstance(CLSID_FolderShortcut, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
	hRes = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
}

if(SUCCEEDED(hRes))
{
	IPersistFile* ppf;
	psl->SetPath(pathToObj);
	psl->SetDescription(description);
	psl->SetHotkey(hotkey);
	psl->SetArguments(cmdLine);
	//Query IShellLink for IPersistFile interface
	hRes = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
	if(SUCCEEDED(hRes))
	{
		//Build the output path
		wchar_t* fname = new wchar_t[MAX_PATH];
		wchar_t* drive = new wchar_t[MAX_PATH];
		_wsplitpath(pathToObj, drive, NULL, fname,NULL); 

		if(wcscmp(fname,L"") != 0)//For files
		{
			wcscat_s(path, MAX_PATH, fname);
		}
		else if(wcscmp(drive,L"") != 0)//For drives
		{
			wchar_t d[2];
			d[0] = drive[0];
			d[1] = '\0';
			wcscat_s(path, MAX_PATH, d);
		}
		delete [] drive;
		delete [] fname;
		wcscat_s(path, MAX_PATH, L".lnk");
		
		ppf->Save(path, TRUE);
		ppf->Release();
	}
	psl->Release();
}
else
{
	ReportError("CoCreateInstance Error");
	return 0;
}
return 1;

4.2 Non file base

The non file base shortcut creation is handled by the overloaded ::CreateLinkToPrinter method. SHGetSpecialFolderLocation() is used to get the location of installed printers by setting it second parameter to CSIDL_PRINTERS. An IShellFolder object is BindToObject() to the LPITEMIDLIST set by the SHGetSpecialFolderLocation() function.

The IShellFolder object is enumerated and each name of installed printer (which is gotten from the GetDisplayNameOf() of the IShellFolder) is compared with the input name (printerName) and if they match, the LPITEMIDLIST of the printer is appended (using the ::Append method) to the LPITEMIDLIST of the printer folder.

Appending the two <code>LPITEMIDLIST is very essential because the SetIDList of IShellLink interface requires an absolute LPITEMIDLIST but the one gotten from the enumeration of the IShellFolder object is relative to the printer folder.

According to MSDN, to create a shortcut to a non-file object, set theSetIDList to the LPITEMIDLIST of the object instead of setting the SetPath() in case of file base object and all other things remain the same.

C++
HRESULT hr = S_OK;
ULONG celtFetched;
LPITEMIDLIST pidlItems = NULL;
LPITEMIDLIST netItemIdLst = NULL;
IShellFolder* pPrinterFolder = NULL;
IEnumIDList* pEnumIds = NULL;
	
IShellFolder* pDesktopFolder = NULL;	
LPITEMIDLIST full_pid;

	
hr = SHGetMalloc(&pMalloc);
hr = SHGetDesktopFolder( &pDesktopFolder );
hr = SHGetSpecialFolderLocation( NULL, CSIDL_PRINTERS, &netItemIdLst );
hr = pDesktopFolder->BindToObject( netItemIdLst, NULL, IID_IShellFolder, (void **)&pPrinterFolder );
	
//Appending '/' to pathToLink if not present
memset(path, '\0', MAX_PATH);
_wmakepath_s(path, MAX_PATH, NULL, pathToLink, NULL, NULL);
	
//***create the directory
if(forceCreate)
{
	createDir(pathToLink);
}

if(SUCCEEDED(hr))
{
	hr = pPrinterFolder->EnumObjects( NULL, SHCONTF_NONFOLDERS, &pEnumIds );
	
	if(SUCCEEDED(hr))
	{
		STRRET strDisplayName;
			
		while((hr = pEnumIds->Next( 1, &pidlItems, &celtFetched)) == S_OK && celtFetched > 0)
		{
			hr = pPrinterFolder->GetDisplayNameOf(pidlItems, SHGDN_NORMAL, &strDisplayName);
			if(SUCCEEDED(hr))
			{
				if(_wcsicmp(strDisplayName.pOleStr, printerName) == 0)
				{
					wcscat_s(path, MAX_PATH, strDisplayName.pOleStr);
					wcscat_s(path, MAX_PATH, L".lnk");
					full_pid=Append(netItemIdLst, pidlItems);
					
					//Create the shortcut
					CreateLink(full_pid, path, description, hotkey);
				}
			}
		}
	}
}
return 0;

5.0 Conclusion

The ::ReportError is used to track error. It only print the error message to screen using printf(), you can rewrite it to suit your need. 

6.0 History

19 July, 2013:

  1. Location constants (CSC_*) were introduced.
  2. Argument BOOL forceCreate (which default to FALSE) was introduced

11 July, 2013: 

  1. Full ANSI and Wide Character versions included.
  2. Early unloading of some DLLs was detected when a single object of the class was used more than once. This will cause application to stop working with debug information "An invalid exception handler routine has been detected (parameters: 0x00000003)". To fix this CoInitializeEx was moved to the class constructor and CoUninitialize to the destructor. 

22 May, 2013:

  1. Initial project.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)