Monday, May 3, 2010

Identifying used and unused resources in a Visual Studio's project Resources.resx file

If you often use project resources in a Visual Studio project, be it VB or C#, eventually you will end up with a big resource file (e.g. Resources) containing many unused items. This is because, when you remove the code that uses the resoure, very often you will forget to remove the actual resource item.

There are solutions to this problems such as using commercial code refactoring tool or making some minor modifications to Resources.Designer.vb/Resources.Designer.cs and relying on the compiler to generate warnings about unused resources. In this post, I choose to take a different approach: use a batch script.

My complete batch script will accept 4 parameters from the command line, listed from left to right:

1. Path to the VB.NET source code files, e.g. WindowsApplication1\*.vb
2. Name of project resource file, e.g. WindowsApplication1\My Project\Resources.resx
3. Name of file where all resources' usage are written, e.g. all_resources.txt
4. Name of file where all unused resources are written, e.g. unused_resources.txt

It makes use of the FOR extended syntax (FOR /F) to parse the resource file and FINDSTR to find where all the resources are referred to. It will only work properly with VB source code files, where most developers often use My.Resources.XXXX to access the resources. As FINDSTR simply performs a string search, the batch script will not care about code that are commented out, as well as resources that are accessed not by using the Vb's My namespace. If you want to use this with C# source code, you'll need to edit the call to FINDSTR to match your method of accessing the resources, e.g. Project1.Resources.Resource1 instead of My.Resources.Resource1.

The rest of this post will analyse some interesting points I encountered when writing it - hope this will be useful for those having similiar problems.

Batch script - powerful despite being crude

Many people may think that batch script is a thing of the past, since there are so many alternatives today - Windows PowerShell, Perl, or a .VBS file. Yet sometimes I still use it due to its powerful but simple nature. Take a look at the code to search for the resource:

FOR /F "tokens=*" %%a IN (%TMPFILE%) DO (
.........
        FINDSTR /S /P /N /C:"My.Resources.%%c" %SRCPATH% >> %OUT_ALLTOKENS%                  
        REM FindStr returns an errorlevel of 0 if the string was found, 1 and above if it was not found.
        IF ERRORLEVEL 1 (
.........
        )
.............      
    )
)


The call to FOR /F
and FINDSTR can easily translate into 10-20 lines of .NET code, unless you use some existing text processing libraries. Here, almost everything is done, just simply remember the syntax, available via FOR /? or FINDSTR /?.

Another example is how to generate a random file name:

:GETTEMPNAME
set TMPFILE=%TMP%\mytempfile-%RANDOM%-%TIME:~6,5%.tmp
if exist "%TMPFILE%" GOTO :GETTEMPNAME


The equivalent .NET code would be:

string filename = Path.GetTempFileName()

However, batch scripts are still very crude. For example, although Windows XP batch scripts allow the use of block statement via (........), don't expect everything to work as they do in a modern programming language. The following would cause problem:

IF (%1) == () (
    ECHO Missing first parameter: path to the VB.NET source code files (e.g. WindowsApplication1\*.vb)
    EXIT /B 1
) ELSE (
    SET SRCPATH=%1
)

Execution would terminate shortly upon reaching ECHO, without any indication why. I believe it would take many people some time to figure out what causes it and fix it. The following will work:

IF (%1) == () (
    ECHO Missing first parameter: path to the VB.NET source code files, e.g. WindowsApplication1\*.vb     
    EXIT /B 1
) ELSE (
    SET SRCPATH=%1
)

Also the above example demonstrates how to check for missing parameter. This would have made more sense in C# or VB.NET, just compare against an empty string (e.g. "").

There are many other useful code snippets in this batch script, for example, how to use delayed environment variable expansion, or how to use double quotes as delimiters in FOR /F. I will not explain it in details - the code is well commented, take some time to study it yourself.

9 comments:

  1. Thank you very much for sharing. I was having a similar problem and used your script and it worked perfectly.

    OB

    ReplyDelete
  2. Works great, I just made the following couple of changes and it worked on my csharp files with the resource files not being the default Resource file.

    SET RESOURCENAME=%~n2

    and

    findstr /S /P /N /C:"%RESOURCENAME%.%%c" %SRCPATH% >> %OUT_ALLTOKENS%

    ReplyDelete
  3. File is not available anymore

    ReplyDelete
  4. The link has been updated.

    The old link is removed due to Mediafire randomly closing my account without any warnings

    ReplyDelete
  5. Can u please give the same code for C# files.

    ReplyDelete
  6. Surprised how well this worked.
    For C# finding in .xaml and .cs I added:
    SET RESOURCENAME=%~n2
    Changed:
    findstr /S /P /N /C:"%RESOURCENAME%.%%c" %SRCPATH% >> %OUT_ALLTOKENS%
    Called it by:
    readprojresx.bat c:\myDir\*.* C:\myDir\Assets\ApplicationStrings.resx C:\Users\Me\Downloads\found.txt C:\Users\Me\Downloads\notFound.txt

    ReplyDelete
    Replies
    1. Where do you add those new lines in batch file?

      Delete
    2. SET RESOURCENAME=%~n2

      can be added just above the FINDSTR call.

      The existing FINDSTR line in the batch file should be modified to the above value.

      Delete
  7. Hi Brent, thanks a lot for sharing your modifications to make it work with xaml files :)

    ReplyDelete