Is there is a way to change a Windows folder icon using a Perl script? Is there is a way to change a Windows folder icon using a Perl script? windows windows

Is there is a way to change a Windows folder icon using a Perl script?


You sure can do it with Perl. Windows controls directory icons by use of a hidden systemDekstop.ini file in each folder. The contents looks something like this:

 [.ShellClassInfo] IconFile=%SystemRoot%\system32\SHELL32.dll IconIndex=41

On Windows XP (and I assume on other systems), icon 41 is a tree. Windows requires this file be explicitly set as a system file for it to work, this means we'll need to dig down into Win32API::File to create it:

 #!/usr/bin/perl use strict; use warnings; use Win32API::File qw(createFile WriteFile fileLastError CloseHandle); my $file = createFile(      'Desktop.ini',      {           Access     => 'w',        # Write access           Attributes => 'hs',       # Hidden system file           Create     => 'tc',       # Truncate/create      } ) or die "Can't create Desktop.ini - " . fileLastError(); WriteFile(      $file,      "[.ShellClassInfo]\r\n" .      "IconFile=%SystemRoot%\\system32\\SHELL32.dll\r\n" .      "IconIndex=41\r\n",      0, [], [] ) or die "Can't write Desktop.ini - " . fileLastError(); CloseHandle($file) or die "Can't close Desktop.ini - " . fileLastError();

If you run the code above, it should set the icon for the current directory to a tree. You may need to refresh your directory listing before explorer picks up the change.

Now that we have a way to change icons, we can now just walk through a whole drive and change every folder that matches our pattern. We can do this pretty easily with File::Find, or one of its alternatives (eg, File::Find::Rule, or File::Next):

 #!/usr/bin/perl use strict; use warnings; use File::Find qw(find); use Win32API::File qw(createFile WriteFile fileLastError CloseHandle); my $topdir = $ARGV[0] or die "Usage: $0 path\n"; find( \&changeIcon, $topdir); sub changeIcon {     return if not /documents$/i;   # Skip non-documents folders     return if not -d;              # Skip non-directories.     my $file = createFile(         "$_\\Desktop.ini",         {              Access     => 'w',        # Write access              Attributes => 'hs',       # Hidden system file              Create     => 'tc',       # Truncate/create         }     ) or die "Can't create Desktop.ini - " . fileLastError();     WriteFile(         $file,         "[.ShellClassInfo]\r\n" .         "IconFile=%SystemRoot%\\system32\\SHELL32.dll\r\n" .         "IconIndex=41\r\n",         0, [], []     ) or die "Can't write Desktop.ini - " . fileLastError();     CloseHandle($file) or die "Can't close Desktop.ini - " . fileLastError(); }

Unfortunately, I've just discovered that the icon only gets changed if the directory currently has, or once had, an icon... There's clearly an attribute that's being set on the directory itself that causes Windows to look for a Desktop.ini file, but I can't for the life of me figure out what it is. As such, the above solution is incomplete; we also need to find and fix the attributes on the directory where we're adding the icon.

Paul


1.

[.ShellClassInfo]LocalizedResourceName=@%SystemRoot%\system32\shell32.dll,-21790InfoTip=@%SystemRoot%\system32\shell32.dll,-12689IconResource=%SystemRoot%\system32\imageres.dll,-108IconFile=%SystemRoot%\system32\shell32.dllIconIndex=-237

2.

[.ShellClassInfo]LocalizedResourceName=@%SystemRoot%\system32\shell32.dll,-21803InfoTip=@%SystemRoot%\system32\shell32.dll,-12689IconResource=%SystemRoot%\system32\imageres.dll,-3


To get the icon to refresh, you will have to invoke some SHChangeNotify voodoo (C++ example, but you get the idea):

int imageIndex = Shell_GetCachedImageIndexW(wPath, GetSyncFolderIconIndex(), 0);if (imageIndex != -1){    // If we don't do this, and we EVER change our icon, Explorer will likely keep    // using the old one that it's already got in the system cache.    SHChangeNotify(SHCNE_UPDATEIMAGE, SHCNF_DWORD | SHCNF_FLUSHNOWAIT, &imageIndex, NULL);}SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW | SHCNF_FLUSHNOWAIT, wPath, NULL);