62

How do you go about finding unused icons, images, strings in .resx files that may have become 'orphaned' and are no longer required?

splattne
  • 102,760
  • 52
  • 202
  • 249
mrpbody
  • 1,016
  • 1
  • 9
  • 9

10 Answers10

52

Recently ResXManager 1.0.0.41 added a feature to show the number of references to a string resource.

Screenshot showing the new reference column

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
hultqvist
  • 17,451
  • 15
  • 64
  • 101
  • 1
    This tool is amazing! – Steve Bauman Aug 14 '18 at 16:27
  • Absolutely. What a lifesaver. It not only shows you the number of uses in the column, but it lets you sort by number of uses. Then you can block select all the "0" usages and delete with one keystroke. In this way I managed to remove over 300 unused strings from a solution in a matter of seconds. I just donated money to them. I probably will again. Fantastic! – Joe Oct 17 '20 at 17:21
  • 1
    Any idea, why this tool does not find all references? Does it require the code files to be in specific folders or somethings? Seems to be searching through App_Code folder, but not others. We have some .aspx files in the root and some user controls in other folders. – DusanV Oct 28 '20 at 17:56
  • I've been using ResXManager for a long time but had never noticed this feature! Great! – tedebus Dec 10 '20 at 11:14
21

I couldn't find any existing solution that would search for string resource references in XAML files and batch-remove unused ones.

So I wrote this: https://github.com/Microsoft/RESX-Unused-Finder

RESX Unused Finder screenshot

It searches a project directory for references to string resources, then displays a list of ones it couldn't find a match for. You can specify a template to search for so it can find references in XAML files.

RandomEngy
  • 14,931
  • 5
  • 70
  • 113
  • +1. Would give more if it was possible. Was looking for a great tool for a while now and this one stands out for me! Great job ! – DeMama May 19 '15 at 21:50
  • This tool is great especially if you are using WPF. We generate classes that proxy for the AppResources and this tool handles it perfectly. We use custom XAML markup extensions and it works with that too. Very well done. – Skrymsli Sep 22 '15 at 18:58
  • Several issues - does not build, but add a reference to System.Windows.Interactivity. uncheck Sign the ClickOnce manifests, uncheck Enable ClickOnce security. After that the solution builds. – AH. Feb 22 '19 at 08:55
  • @AH. Thanks. I removed the signing (that was using expired temp cert) and updated the ClickOnce hosting location. I think it will run into some smartscreen warnings but hopefully they will go away as it gets used more often. – RandomEngy Feb 23 '19 at 21:02
  • Otherwise great app. Thanks. – AH. Feb 25 '19 at 05:52
11

I created a free open-source VS extension that looks for unused images in a project, just published the first version: https://marketplace.visualstudio.com/items?itemName=Jitbit1.VSUnusedImagesFinder

Alex from Jitbit
  • 53,710
  • 19
  • 160
  • 149
8

Since I could not find a simple and fast solution yet, I found at least a solution that allows me to get the result I'm looking for, even if it takes some time (ideal for a lazy sunday afternoon).

The solution involves Visual Studio .NET 2010 and ReSharper (I'm using version 7.1) and goes like the following.

Step-by-step solution

1.) Right-click your primary RESX file in VS.NET and select "Find Usages" from the context menu:

enter image description here

This will bring up ReSharper's "Find Results" window.

2.) Double-click each occurrence in the solution window:

enter image description here

This will open the source code window with the resource.

3.) Rename this resource from within the source code window:

enter image description here

It will bring up ReSharper's "Rename Resource" dialog.

4.) Give the resource a new name with a unique prefix. In my example this is "TaskDialog_":

enter image description here

It will rename both the resource and also the auto-generated C# wrapper/access class.

5.) Repeat the above steps 2, 3 and 4 for all resources in the "Usages" window.

6.) Open the RESX file in Visual Studio's resource editor and select all files without the prefix:

enter image description here

7.) Now click the "Remove Resource" button on the top of the window or simply press the Del key:

enter image description here

You finally have a RESX file with only the acutally used resources in your file.

8.) (Optionally) If you have resources in multiple languages (e.g. "Resources.de.resx" for German), repeat steps 7 and 8 for those RESX files, too.

Warning

Please note that this will not work if you access your strings other than through the strongly-typed, auto-generated C# class Resources.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
8

This is no information an algorithm can reliably compute. The inspected program could fetch a list of all resources and do something with them, like letting the user choose from several icons.

Your best bet is probably to search for all references to your resource-access API of choice and inspect those manually. Using grep/sed you might be able to reduce the sites you have to inspect manually by handling all "easy" ones where a simple string is used.

David Schmitt
  • 58,259
  • 26
  • 121
  • 165
  • 1
    This was never true as the autogenerated Designer file is a class that you can validate references against its methods to find what you need. If you are on a new Visual Studio it's even easier if you have CodeLens. – Rodney S. Foley Nov 08 '19 at 20:57
  • @RodneyS.Foley "never true" is strong language, especially considering you follow up with something that is true if and only if the app is only using the autogenerated designer, which is only a small subset of possibilities how a resource could be used in an app. – David Schmitt Nov 13 '19 at 10:21
  • 1
    "This is no information an algorithm can reliably compute." is not a true statement. There are free extensions, one in particular, that use an algorithm that reliably does compute this. Visual Studio can and does compute this, Resharper can and does compute this. – Rodney S. Foley Nov 13 '19 at 23:49
6

I recently built a tool that detects and removes unused string resources. I used the information in this post as a reference. The tool may not be perfect, but it does the heavy-lifting part and will be useful if you have a big project with the long history. We used this tool internally to consolidate resource files, and remove unused resources (we got rid of 4,000+ resources out of 10,000).

You can look at the source code, or just install ClickOnce from here: https://resxutils.codeplex.com/

votrubac
  • 141
  • 1
  • 3
  • 4
  • 2
    This looked promising (once I found you had to delete Resources.Designer.cs to get it to show any results). But it could not find references in XAML files, which is a dealbreaker. – RandomEngy Sep 06 '14 at 16:13
3

I had a similar problem. Several thousand resource strings that I'd created for a translation table, many of which were no longer required or reference by code. With around 180 dependent code files, there was no way I was going to manually go through each resource string.

The following code (in vb.net) will go through your project finding orphaned resources (in the project resources, not any individual forms' resources). It took around 1 minute for my project. It can be modified to find strings, images or any other resource type.

In summary it;

  • 1) Uses the solution project file to gather all the included code modules and appends them into a single string variable;
  • 2) Loops through all the project resource objects, and creates a list (in my case) of those which are strings;
  • 3) Does a string search finding resource string codes in the combined project text variable;
  • 4) Reports resource objects that are not referenced.

The function returns the object names on the windows clipboard for pasting in a spreadsheet or as a list array of the resource names.

edit : example call in module : modTest
? modTest.GetUnusedResources("C:\Documents and Settings\me\My Documents\Visual Studio 2010\Projects\myProj\myProj.vbproj", True, true)

'project file is the vbproj file for my solution
Public Function GetUnusedResources(projectFile As String, useClipboard As Boolean, strict As Boolean) As List(Of String)


    Dim myProjectFiles As New List(Of String)
    Dim baseFolder = System.IO.Path.GetDirectoryName(projectFile) + "\"

    'get list of project files 
    Dim reader As Xml.XmlTextReader = New Xml.XmlTextReader(projectFile)
    Do While (reader.Read())
        Select Case reader.NodeType
            Case Xml.XmlNodeType.Element 'Display beginning of element.
                If reader.Name.ToLowerInvariant() = "compile" Then ' only get compile included files
                    If reader.HasAttributes Then 'If attributes exist
                        While reader.MoveToNextAttribute()
                            If reader.Name.ToLowerInvariant() = "include" Then myProjectFiles.Add((reader.Value))
                        End While
                    End If
                End If
        End Select
    Loop

    'now collect files into a single string
    Dim fileText As New System.Text.StringBuilder
    For Each fileItem As String In myProjectFiles
        Dim textFileStream As System.IO.TextReader
        textFileStream = System.IO.File.OpenText(baseFolder + fileItem)
        fileText.Append(textFileStream.ReadToEnd)
        textFileStream.Close()
    Next
    '  Debug.WriteLine(fileText)

    ' Create a ResXResourceReader for the file items.resx.
    Dim rsxr As New System.Resources.ResXResourceReader(baseFolder + "My Project\Resources.resx")
    rsxr.BasePath = baseFolder + "Resources"
    Dim resourceList As New List(Of String)
    ' Iterate through the resources and display the contents to the console.
    For Each resourceValue As DictionaryEntry In rsxr
        ' Debug.WriteLine(resourceValue.Key.ToString())
        If TypeOf resourceValue.Value Is String Then ' or bitmap or other type if required
            resourceList.Add(resourceValue.Key.ToString())
        End If
    Next
    rsxr.Close()  'Close the reader.

    'finally search file string for occurances of each resource string
    Dim unusedResources As New List(Of String)
    Dim clipBoardText As New System.Text.StringBuilder
    Dim searchText = fileText.ToString()
    For Each resourceString As String In resourceList
        Dim resourceCall = "My.Resources." + resourceString ' find code reference to the resource name
        Dim resourceAttribute = "(""" + resourceString + """)" ' find attribute reference to the resource name
        Dim searchResult As Boolean = False
        searchResult = searchResult Or searchText.Contains(resourceCall)
        searchResult = searchResult Or searchText.Contains(resourceAttribute)
        If Not strict Then searchResult = searchResult Or searchText.Contains(resourceString)
        If Not searchResult Then ' resource name no found so add to list
            unusedResources.Add(resourceString)
            clipBoardText.Append(resourceString + vbCrLf)
        End If
    Next

    'make clipboard object
    If useClipboard Then
        Dim dataObject As New DataObject ' Make a DataObject clipboard
        dataObject.SetData(DataFormats.Text, clipBoardText.ToString())        ' Add the data in string format.
        Clipboard.SetDataObject(dataObject) ' Copy data to the clipboard.
    End If

    Return unusedResources

End Function
AjV Jsy
  • 5,799
  • 4
  • 34
  • 30
  • Thank you, hope you don't mind me adding an extra note or two. We had a form with a couple of thousand resource strings which we needed to investigate, so this was useful along the way. Turned out that the form had its Localizable property True so all the controls had settings stored in the form's resx. Setting to false switched things back to normal and shrank the resz size down from 600+k to 35k! – AjV Jsy Jul 02 '15 at 15:18
3

I use ReSharper for finding unused resource fields and then remove them manually if project contains small amount of resources. Some short script can be used if we already have list of unused items.

The solution is next:

  • show all unused members as described in this article
  • temporary remove *.Designer.cs from Generated file masks (ReSharper → Options → CodeInspection → GeneratedCode)
  • Also comment or remove comment (that indicates that code is auto generated) from top of Designer.cs file attached to resource file.

You will have list of all unused resources, left to remove them from resx.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Anatoliy
  • 672
  • 5
  • 12
1

I've been considering this myself and I believe I have two options. Both of these rely on the fact that I use a helper method to extract the required resource from the resource files.

  1. Logging
    Add some code to the "getresource" method or methods so that every time a resource is accessed, the resource key is written to a log. Then try to access every part of the site (a testing script might be helpful here). The resultant log entries should give a list of all the active resource keys, the rest can be junked.

  2. Code Analysis
    I am looking at whether T4 is capable of working through the solution and creating a list of all references to the "getresource" helper method. The resultant list of keys will be active, the rest can be deleted.

There are limitations of both methods. The logging method is only as good as the code covered by the test and the code analysis might not always find keys rather than strings containg the keys so there will be some extra manual work required there.

I think I'll try both. I'll let you know how it goes.

Chris Simpson
  • 7,821
  • 10
  • 48
  • 68
1

Rename your current image directory and then create a new one, do a find-in-files search within VS for your image path, i.e. '/content/images', multiselect all the used images and drag them into the new image folder. You can then exclude the old directory from the project, or just delete it.

DevHead
  • 21
  • 1