Programming Desktop Restore
The source code for Desktop Restore cannot be released due to some unspecified commitments, but I would like to discuss some of the techniques used.  Bear in mind that much of this information is undocumented by Microsoft and could change at any time in the future.  When possible, you should always use only documented API's.  Having said that, I haven't found a way to write Desktop Restore using only the documented features.

See also the excellent article from Raymond Chen: Manipulating the positions of desktop icons

Example Code

Process Address Space

The Desktop Icon Layout is really just a ListView control, in Icon mode.  It is running in the process of Windows Explorer.  While it is easy enough to obtain a handle to the desktop Window and send it messages, it is much more involved if you need  to access or modify the desktop ListView structures and memory from a separate process.

In the Desktop Restore Shell Extension DLL, dkticnsr.dll, this access is trivial because dkticnsr.dll runs in the context of Windows Explorer.  This is because it is loaded as a COM object as Explorer is loaded, and runs in response to messages from the Explorer context menu on the Desktop.

The CommandLine version is actually much more complicated than the shell extension, internally, as it is running in its own process.  In order to reliably read and write memory in the Desktop Explorer process, shared memory access must be employed. DesktopCmd.exe accomplishes this by way of calls to VirtualAllocEx(), ReadProcessMemory(), and  WriteProcessMemory().  For now we'll discuss the techniques used by the Shell Extension, dkticnsr.dll.

Following is the method I use to reliably find the Window Handle to the Desktop ListView (C++). Experience has shown that this views hierarchy can change for unknown reasons, so I enumerate all windows below Progman to find SHELLDLL_DefView.  Once located, it is always the parent of the SysListView32 containing the desktop icon objects.

The Window names and hierarchy were determined by using the VC++ tool, Windows Spy++:

HWND GetDesktopListView()
{
   HWND hWndPM = FindWindowEx( NULL, NULL, _T("Progman"), NULL );
   if (!hWndPM)
	return NULL;

   HWND hWnd = FindWindowEx( hWndPM, NULL, _T("SHELLDLL_DefView"), NULL );
   if (!hWnd)
   {
      // Under Win2K this seems to move around, and there can be many WorkerW windows
      EnumWindows( FindDLV, LPARAM((HWND*)&hWnd) );
      if (!hWnd)
         return NULL;
   }

   HWND hWndLV = FindWindowEx( hWnd, NULL, _T("SysListView32"), NULL );

   return hWndLV;
}
//////////////////////////////////////////////////////////////////////////////////////
BOOL CALLBACK FindDLV( HWND hWndPM, LPARAM lParam )
{
   HWND hWnd = FindWindowEx( hWndPM, NULL, _T("SHELLDLL_DefView"), NULL );
   if (hWnd)
   {
      // This is our window!
      HWND* phWnd = (HWND*)lParam;
      *phWnd = hWnd;
      return FALSE; // all done
   }

   return TRUE; // keep looking
}
//////////////////////////////////////////////////////////////////////////////////////
So now you can just treat the returned window as a ListView and party on it.  Following is an example:
LVITEM lvi;
POINT  pt;
_TCHAR szBuf[ _MAX_PATH ];
for (int ix = ListView_GetNextItem( hWndLV, -1, LVNI_ALL ); ix != -1;
	 ix = ListView_GetNextItem( hWndLV, ix, LVNI_ALL ))
{
   ZeroMemory( &lvi, sizeof( LVITEM ) );
   szBuf[ 0 ]  = _T('\0');

   lvi.iItem = ix;
   lvi.mask  = LVIF_TEXT | LVIF_PARAM;
   lvi.pszText = szBuf;
   lvi.cchTextMax = MAX_PATH; 
    
   if (!ListView_GetItem( hWndLV, &lvi ))
      continue;

   ZeroMemory( &pt, sizeof( POINT ) );
   if (!ListView_GetItemPosition( hWndLV, ix, &pt ))
      continue;

   // ...
}
Back to Desktop Restore Page
Copyright © 1995-2020 by Jamie O'Connell. All rights reserved.
email: webmaster@_REMOVE_midiox.com
This page was last modified on 01-11-08

 

Although the above will obtain the name and position of each icon, the name is not necessarily unique. You can have a directory and an icon shortcut with the same name, for instance.  To find out the actual underlying name of the object we will need to consult the Shell.  Note that in the above code I am requesting the LVIF_PARAM in addition to the LVIF_TEXT for the icon name.  It turns out that on all modern versions of Windows the lParam value is really a pointer to an ITEMIDLIST (PIDL) . This is undocumented and may change in the future (update: it has changed for Vista -- it is always NULL. For Vista we use the old technique).  I guessed it might be a LPCITEMIDLIST to aid in sorting the items, and that guess turned out to be correct.  You can't just assume that this LPCITEMIDLIST will refer to a file or shortcut though, some desktop objects (My Computer, Recycle bin, ...) are registered desktop items, referred to by a GUID.

Following is some code I use to determine this and obtain the Shell Item ID (SHITEMID) for the item.  The first thi COM pointer to the Desktop folder:

// ... in loop
// See if it's a normal file or dir
LPCITEMIDLIST pidl = LPCITEMIDLIST(lvi.lParam);
if (pidl == NULL) // then this is Vista or above
{
   // In this case, save in the registry the old way; Name - Value: encoded DWORD
   // Code ommitted...
   
   continue; // after saving just loop to the next icon
}
   				
CString strValName;
WIN32_FIND_DATA fd = { 0 };
HRESULT hr = SHGetDataFromIDList( pSHDesk, pidl, SHGDFIL_FINDDATA, &fd, sizeof( WIN32_FIND_DATA ) );
if (SUCCEEDED( hr ))
{
   strValName = fd.cFileName;
}
else 
{	
   // No, see if it's a Registered desktop item with a Guid
   SHDESCRIPTIONID dd = { 0 };
   hr = SHGetDataFromIDList( pSHDesk, pidl, SHGDFIL_DESCRIPTIONID, &dd, sizeof( SHDESCRIPTIONID ) );
   if (SUCCEEDED( hr ))
   {
      strValName.FromGUID( dd.clsid );
   }
}

if (!SUCCEEDED( hr ) || strValName.IsEmpty())
   continue; // Failure

// ... Now you can just store the obtained information: Item ID, Name, Position 
As promised, here's the custom FromGUID() method.  It involves some tricky printf style formatting:
void CString::FromGUID( const GUID& guid )
{
   Format( _T("{%-08.8X-%-04.4X-%-04.4X-%-02.2X%-02.2X-%02.2X%-02.2X%-02.2X%-02.2X%-02.2X%-02.2X}"), 
         guid.Data1, guid.Data2, guid.Data3, guid.Data4[ 0 ], guid.Data4[ 1 ],
	 guid.Data4[ 2 ], guid.Data4[ 3 ], guid.Data4[ 4 ], guid.Data4[ 5 ], 
	 guid.Data4[ 6 ], guid.Data4[ 7 ] );
}

 

Then, inside the loop, after retrieving the Item and Name, you can query the shell for the object.  Note that the and Name, you can query the shell for the object.  Note that the CString class in the code is not the MFC CString (this applet does not use MFC), but a custom class I have crafted for my needs.  In particular, the FromGUID() member is custom and will be listed below:

CComPtr<IShellFolder> pSHDesk;
if (!SUCCEEDED( SHGetDesktopFolder( &pSHDesk ) ))
   return;