Appending CRC32 Hash to End of File Name

I am attempting to write a button that finds the CRC32 hash of the selected file, and then appends that hash to the end of the file's name.

For example, I have a text file named "test.txt", which has a CRC value of B919EA13.

I want to make a button that can rename test.txt into "test [B919EA13].txt"

Because DOpus does not (to my knowledge) have a built-in CRC checker, I made a button that uses a command line tool to output the CRC value to a text file, and then calls PowerShell so that all but the CRC value is erased and only the CRC is copied to the clipboard, followed by erasing the generated text file. The problem I'm hoping someone can help with is how to make the button then take the value that is on the clipboard and append it to the end of the filename.

Here's the code I have so far:

<?xml version="1.0"?>
<button backcol="none" display="both" textcol="none">
	<label>Add CRC32 to Filename</label>
	<tip>Finds the CRC32 value of the selected file, and appends that value to the filename.</tip>
	<icon1>X:\icon.png,0</icon1>
	<function type="batch">
		<instruction>&quot;C:\File Management\crc32.exe&quot; &quot;{filepath$}&quot; -nf &gt; input.txt</instruction>
		<instruction>powershell -command &quot;&amp; {get-content input.txt|select-object -first 1}&quot; | clip</instruction>
		<instruction>del input.txt</instruction>
		<instruction>runmode hide</instruction>
	</function>
</button>

Can anyone help find a way to append the clipboard data to the filename with this?

Thanks in advance.

Does it need to be CRC32?

Opus can calculate MD5, sha1, sha256, and sha512.

We could probably add CRC32. It isn't used for much these days but it's easy enough to calculate.

Yes, I absolutely have a need for it to be CRC32, for backwards compatibility reasons among other things. I have a client who has thousands of files with CRC hashes on the end of the filenames, and needs to have the newly created files I'm sending him to be have the same.

I send him these kinds of files quite often, sometimes several times a day, thus my wanting to have a button to simplify the process. As of right now, I either have to install a buggy Windows shell extension or go through multiple steps in manually copying the info from the command line each and every time.

If you could add CRC32 checking to Opus natively, that'd be great! I was trying to write this button using batch functionality as a stopgap since Opus doesn't include CRC checking.

This will work in the next update (12.12.3):

As a button:

(Set type to Standard Function (Opus or External). Although there is script code in the button, the main command at the top is a normal command, and the stuff from the @script jscript line onwards is a rename script embedded in the button. The button isn't a Script Function.)

@nodeselect
Rename PATTERN * TO *
@script jscript
var fsu = DOpus.FSUtil;
function OnGetNewName(getNewNameData)
{
	var item = getNewNameData.item;
	if (item.is_dir)
	{
		return;
	}

	// Alternatives are "crc32_php" and "crc32_php_rev"
	var crc = fsu.Hash(item.realpath, "crc32");

	if (crc == null || crc == "")
	{
		return;
	}

	crc = "[" + crc.toUpperCase() + "]";

	// Check if the name already has this CRC.
	if (getNewNameData.newname.indexOf(crc) != -1)
	{
		return;
	}

	return getNewNameData.newname_stem_m + " " + crc + getNewNameData.newname_ext_m;
}

Or, you can also use the same script in the Rename dialog, if you want to create a Rename Preset instead of or as well as a button.

(You could make a button which runs Rename PRESET="My Preset Name to run the preset from a button, if you go this path.)

In normal use:

Script editor open:


CRC32 is not a standard, so I don't know which version of CRC32 your current tool uses. We've added the three versions that seem most common. See the code comments for details on how to specify each one. I would try all three to find the one that (hopefully!) matches the tool you are using:

  • CRC32 is most common in the Windows world and matches what tools like 7-Zip and PKZip call "CRC32", and what PHP calls "CRC32b".
  • CRC32_PHP is less common and matches what BZIP2 uses and what PHP outputs by default
  • CRC32_PHP_REV is the same as CRC32_PHP but reverses the byte-order of the result, as output by some tools.

If you want to upload a test file and expected checksum, I can verify that one of the modes we've added will match what you're expecting. Please zip the file to make sure the forum doesn't modify it, as the forum will modify some file formats (e.g. images, especially).


The script checks if the checksum is already somewhere in the filename and won't add it twice, so it can be used on files that already have their checksums without duplicating them. Of course, if the file contents change then it will add a new checksum on the end. You could modify that logic if/as needed.

1 Like

Wow, you developers are super quick and detailed on this! Thank you so much for adding this! I look forward to v 12.12.3 :slight_smile:
To be honest, I didn't even know there were more than one variant of CRC32...:open_mouth:

I've attached a Sample Txt Document for DOpus Devs.zip (322 Bytes) here in a zip file so you can check to which version of CRC32 it is. My current buggy shell extension tells me the CRC of the txt file inside should be 4A91476C. I'm interested in knowing which form of CRC32 I've been using all this time.

As a related question, I'm guessing the function for getting the file's CRC is by calling fsu.Hash(item.realpath, "crc32")?
Or is there a specific variable that can be called like {clip}?

The reason I ask is because my second goal in writing this button after figuring out how to have it append the information to the file name, is to be able to retrieve the CRC value from the filename using regex, compare it to the file's current CRC, and then output a dialogue box stating whether or not the two values match.

My guess would be to use @set to assign the value grabbed via regex to a variable (filenameCRC), and then have something like if filenameCRC = currentCRC, output a dialogue box stating "Checksums match".

Can something like this be done with your new implementation?

That matches our "CRC32" mode, so we're good.

Easily done:

function OnClick(clickData)
{
	var dlg = clickData.func.dlg;
	var cmd = clickData.func.command;
	cmd.deselect = false; // Prevent automatic deselection

	var fsu = DOpus.FSUtil;

	for (var eSel = new Enumerator(clickData.func.sourcetab.selected); !eSel.atEnd(); eSel.moveNext())
	{
		var message;
		var icon;
	
		var item = eSel.item();
		if (item.is_dir)
		{
			continue;
		}

		try
		{
			// Alternatives are "crc32_php" and "crc32_php_rev"
			var crc = fsu.Hash(item.realpath, "crc32");

			crc = "[" + crc.toUpperCase() + "]";

			if (item.name.indexOf(crc) == -1)
			{
				message = 'Missing CRC: ' + crc + ' not in:\n' + item.name;
				icon = "warning";
			}
			else
			{
				message = 'Correct CRC:\n' + item.name;
				icon = "info";
			}
		}
		catch(e)
		{
			message = 'Error generating CRC for:\n' + item.name;
			icon = "error";
		}

		dlg.title = "CRC Check";
		dlg.message = message;
		dlg.icon = icon;
		dlg.buttons = "OK";
		dlg.Show();
	}
}

In button XML form:

<?xml version="1.0"?>
<button backcol="none" display="both" label_pos="right" textcol="none">
	<label>Test JScript</label>
	<icon1>#script</icon1>
	<function type="script">
		<instruction>@script jscript</instruction>
		<instruction>function OnClick(clickData)</instruction>
		<instruction>{</instruction>
		<instruction>	var dlg = clickData.func.dlg;</instruction>
		<instruction>	var cmd = clickData.func.command;</instruction>
		<instruction>	cmd.deselect = false; // Prevent automatic deselection</instruction>
		<instruction />
		<instruction>	var fsu = DOpus.FSUtil;</instruction>
		<instruction />
		<instruction>	for (var eSel = new Enumerator(clickData.func.sourcetab.selected); !eSel.atEnd(); eSel.moveNext())</instruction>
		<instruction>	{</instruction>
		<instruction>		var message;</instruction>
		<instruction>		var icon;</instruction>
		<instruction>	</instruction>
		<instruction>		var item = eSel.item();</instruction>
		<instruction>		if (item.is_dir)</instruction>
		<instruction>		{</instruction>
		<instruction>			continue;</instruction>
		<instruction>		}</instruction>
		<instruction />
		<instruction>		try</instruction>
		<instruction>		{</instruction>
		<instruction>			// Alternatives are &quot;crc32_php&quot; and &quot;crc32_php_rev&quot;</instruction>
		<instruction>			var crc = fsu.Hash(item.realpath, &quot;crc32&quot;);</instruction>
		<instruction />
		<instruction>			crc = &quot;[&quot; + crc.toUpperCase() + &quot;]&quot;;</instruction>
		<instruction />
		<instruction>			if (item.name.indexOf(crc) == -1)</instruction>
		<instruction>			{</instruction>
		<instruction>				message = &apos;Missing CRC: &apos; + crc + &apos; not in:\n&apos; + item.name;</instruction>
		<instruction>				icon = &quot;warning&quot;;</instruction>
		<instruction>			}</instruction>
		<instruction>			else</instruction>
		<instruction>			{</instruction>
		<instruction>				message = &apos;Correct CRC:\n&apos; + item.name;</instruction>
		<instruction>				icon = &quot;info&quot;;</instruction>
		<instruction>			}</instruction>
		<instruction>		}</instruction>
		<instruction>		catch(e)</instruction>
		<instruction>		{</instruction>
		<instruction>			message = &apos;Error generating CRC for:\n&apos; + item.name;</instruction>
		<instruction>			icon = &quot;error&quot;;</instruction>
		<instruction>		}</instruction>
		<instruction />
		<instruction>		dlg.title = &quot;CRC Check&quot;;</instruction>
		<instruction>		dlg.message = message;</instruction>
		<instruction>		dlg.icon = icon;</instruction>
		<instruction>		dlg.buttons = &quot;OK&quot;;</instruction>
		<instruction>		dlg.Show();</instruction>
		<instruction>	}</instruction>
		<instruction>}</instruction>
	</function>
</button>

Clipboard%20Image Clipboard%20Image%20(1)

It skips folders entirely but could be extended to recurse into them if you wanted.

You could also use a custom dialog to show the results for multiple files at once, in a list, but that's more work.

Thank you, Leo, for all the work on coding the button functions I asked for. I haven't ever had a need to CRC check more than one file at once, or do folders, so this looks like it should work for what I need it for.

I look forward to the beta build that this code will work with, and will let you know how it works when I get to try it! :smiley:

1 Like

I just tried the button functions you provided, and they work great, so thank you!

After testing both functions and finding they worked just as I wanted them to, I made the output messages during the hash comparison be a little more user friendly, then combined both into a single menu button here:

<?xml version="1.0"?>
<button backcol="none" display="icon" label_pos="right" separate="yes" textcol="none" type="menu">
	<label>CRC Checking</label>
	<icon1>/hostpictures/DOpus Icons/hashcheck.png,0</icon1>
	<button backcol="none" display="both" label_pos="right" textcol="none">
		<label>Add CRC to End of Filename</label>
		<tip>Find the current CRC32 value of the selected file and append that value to the end of the filename for later comparison.</tip>
		<icon1>/hostpictures/DOpus Icons/hashcheck.png,0</icon1>
		<function type="normal">
			<instruction>@nodeselect</instruction>
			<instruction>@disablenosel</instruction>
			<instruction>Rename PATTERN * TO *</instruction>
			<instruction>@script jscript</instruction>
			<instruction>var fsu = DOpus.FSUtil;</instruction>
			<instruction>function OnGetNewName(getNewNameData)</instruction>
			<instruction>{</instruction>
			<instruction>	var item = getNewNameData.item;</instruction>
			<instruction>	if (item.is_dir)</instruction>
			<instruction>	{</instruction>
			<instruction>		return;</instruction>
			<instruction>	}</instruction>
			<instruction />
			<instruction>	// Alternatives are &quot;crc32_php&quot; and &quot;crc32_php_rev&quot;</instruction>
			<instruction>	var crc = fsu.Hash(item.realpath, &quot;crc32&quot;);</instruction>
			<instruction />
			<instruction>	if (crc == null || crc == &quot;&quot;)</instruction>
			<instruction>	{</instruction>
			<instruction>		return;</instruction>
			<instruction>	}</instruction>
			<instruction />
			<instruction>	crc = &quot;[&quot; + crc.toUpperCase() + &quot;]&quot;;</instruction>
			<instruction />
			<instruction>	// Check if the name already has this CRC.</instruction>
			<instruction>	if (getNewNameData.newname.indexOf(crc) != -1)</instruction>
			<instruction>	{</instruction>
			<instruction>		return;</instruction>
			<instruction>	}</instruction>
			<instruction />
			<instruction>	return getNewNameData.newname_stem_m + crc + getNewNameData.newname_ext_m;</instruction>
			<instruction>}</instruction>
		</function>
	</button>
	<button backcol="none" display="both" label_pos="right" textcol="none">
		<label>Compare CRC</label>
		<tip>Find the current CRC32 value of the selected file, and compare that to the expected CRC32 value in the filename if one exists.</tip>
		<icon1>/hostpictures/DOpus Icons/hashcheck.png,0</icon1>
		<function type="script">
			<instruction>@nodeselect</instruction>
			<instruction>@disablenosel</instruction>
			<instruction>@script jscript</instruction>
			<instruction>function OnClick(clickData)</instruction>
			<instruction>{</instruction>
			<instruction>	var dlg = clickData.func.dlg;</instruction>
			<instruction>	var cmd = clickData.func.command;</instruction>
			<instruction>	cmd.deselect = false; // Prevent automatic deselection</instruction>
			<instruction />
			<instruction>	var fsu = DOpus.FSUtil;</instruction>
			<instruction />
			<instruction>	for (var eSel = new Enumerator(clickData.func.sourcetab.selected); !eSel.atEnd(); eSel.moveNext())</instruction>
			<instruction>	{</instruction>
			<instruction>		var message;</instruction>
			<instruction>		var icon;</instruction>
			<instruction>	</instruction>
			<instruction>		var item = eSel.item();</instruction>
			<instruction>		if (item.is_dir)</instruction>
			<instruction>		{</instruction>
			<instruction>			continue;</instruction>
			<instruction>		}</instruction>
			<instruction />
			<instruction>		try</instruction>
			<instruction>		{</instruction>
			<instruction>			// Alternatives are &quot;crc32_php&quot; and &quot;crc32_php_rev&quot;</instruction>
			<instruction>			var crc = fsu.Hash(item.realpath, &quot;crc32&quot;);</instruction>
			<instruction />
			<instruction>			crc2 = crc.toUpperCase();</instruction>
			<instruction />
			<instruction>			crc = &quot;[&quot; + crc.toUpperCase() + &quot;]&quot;;</instruction>
			<instruction />
			<instruction>			if (item.name.indexOf(crc) == -1)</instruction>
			<instruction>			{</instruction>
			<instruction>				message = item.name + &apos;\&apos;s CRC (&apos; + crc2 + &apos;)\neither does not match the expected CRC, or its filename \ndoes not contain an expected CRC value.&apos;;</instruction>
			<instruction>				icon = &quot;warning&quot;;</instruction>
			<instruction>			}</instruction>
			<instruction>			else</instruction>
			<instruction>			{</instruction>
			<instruction>				message = &apos;The expected CRC of:\n&apos; + item.name + &apos; matches the file\&apos;s current CRC.&apos;;</instruction>
			<instruction>				icon = &quot;info&quot;;</instruction>
			<instruction>			}</instruction>
			<instruction>		}</instruction>
			<instruction>		catch(e)</instruction>
			<instruction>		{</instruction>
			<instruction>			message = &apos;Error generating CRC for:\n&apos; + item.name;</instruction>
			<instruction>			icon = &quot;error&quot;;</instruction>
			<instruction>		}</instruction>
			<instruction />
			<instruction>		dlg.title = &quot;CRC Check&quot;;</instruction>
			<instruction>		dlg.message = message;</instruction>
			<instruction>		dlg.icon = icon;</instruction>
			<instruction>		dlg.buttons = &quot;OK&quot;;</instruction>
			<instruction>		dlg.Show();</instruction>
			<instruction>	}</instruction>
			<instruction>}</instruction>
		</function>
	</button>
</button>

I have two questions, though.The first is that I'm unclear on how to make the menu itself not active when no files are selected. As it is now, i can click the button whether or not files are selected, but the menu's options are greyed out if nothing is selected.

My second question is regarding a more specific (and probably more complicated) dialogue. As it is, the CRC check function finds the file's current CRC, then looks for that in the filename. If it's not there, it basically says "not in filename or no CRC in filename", but is there a way to make it be more specific than that?
For example, making it so a name named TestFile[ABCB1234].txt that has a CRC of [9876EDAC] would result in a dialogue outputting "The file's current CRC of [9876EDAC] does not match the expected CRC of [ABCB1234]."

I hope you can help me figure out how to do this.

Add @disablenosel on the Modifiers tab:

You could use this to test if a name contains something that looks like a CRC already:

function hasCrc(n)
{
	return n.match(/\[[0-9A-F]{8}]/) != null;
}

If it has a CRC but it isn't the expected CRC, then you know it has the wrong CRC. (Or something that just looks like a CRC.)