Welcome!

By registering with us, you'll be able to discuss, share and private message with other members of our community.

SignUp Now!

%@verinfo does not work for EXE files compiled from Python

Apr
10
0
I have a folder of EXE and DLL files, some compiled from C++ and others from Python.
1779291062355.webp

The TCC function %@verinfo works fine for the DLLs and EXE files compiled from C++.
But %@verinfo does not work for EXE files compiled from Python. It simply returns an empty string.
1779291275960.webp

I can upload one of these exe files if that helps debug TCC.
 
Does a compiled python .exe have a file version?

I use q.exe daily, which is a python compiled .exe

1779292862186.webp


Code:
C:\...\q-text-as-data>echo %@verinfo[q.exe]
ECHO is OFF

Code:
C:\...\q-text-as-data>fileinfo q.exe
C:\Program Files\q-text-as-data\q.exe

    “Application”  (.EXE)

    35,679,744 bytes     34.03 megabytes

    Created:   Fri 2021-11-26  17:50:14
    Modified:  Fri 2021-11-26  17:50:14
    Accessed:  Wed 2026-05-20  12:01:31

    Attributes:  Archive
    File owner:  NT AUTHORITY\SYSTEM

    CRC-32 :  1F63F9F5
    MD5    :  20372F61C7117924AB6D355AFAAF94A2

    Executable type ......... Windows 64-bit (console)

1 matching file found.

Do your python compiled .exes have version info?

Ref: FileUtils Plugin

Ref: q - Text as Data

Joe
 
By default, Python-compiled .exe files (such as those created with PyInstaller, cx_Freeze, or py2exe) do not include Windows version information like:
  • File Description
  • Product Name
  • Company Name
  • File Version
  • Product Version
When you build an .exe from Python code,
the output is essentially a self-contained executable with the Python interpreter and your code bundled together.

Unless you explicitly add a Windows resource file (.rc) or a version info block,
the executable will have blank or generic metadata.

Source: Microsoft CoPilot

Joe
 
I just did a q.exe -v which shows that the q.exe is version 3.1.6

Code:
C:\...\q-text-as-data>q.exe -v
q version 3.1.6
Python: 3.8.12 (default, Oct 17 2021, 23:47:18) [MSC v.1929 64 bit (AMD64)]
Copyright (C) 2012-2021 Harel Ben-Attia ([email protected], @harelba on twitter)
http://harelba.github.io/q/

Looking at the code,
the version is hard-coded;

1779294074433.webp


Joe
 
According to Microsoft CoPilot;

Even though you configured PyInstaller to “include VersionInfo,”
the resulting EXE does not contain a proper Win32 VERSIONINFO resource block,
which is the only place TCC’s @VERINFO and FILEINFO look.

PyInstaller often stores metadata in the manifest or not at all unless you explicitly provide a .version file and build with the correct options.

Therefore, TCC cannot see the version info — because from the PE resource perspective, it isn’t there.

Joe
 
From Microsoft CoPilot;

To make PyInstaller EXEs readable by TCC:

Create a .version file containing a valid VSVersionInfo block

Build with --version-file=...

Verify the EXE contains an RT_VERSION resource

TCC’s @verinfo and fileinfo will now work

Please confirm if this is how you used pyinstaller to create your .exes

Joe
 
Last edited:
I got it to work on my q.exe file.

I used Resource Hacker to add the following version info;
Code:
1 VERSIONINFO
FILEVERSION 3,1,6,0
PRODUCTVERSION 3,1,6,0
FILEFLAGSMASK 0x3fL
FILEFLAGS 0x0L
FILEOS 0x40004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040904B0"
        BEGIN
            VALUE "CompanyName", "Your Company"
            VALUE "FileDescription", "q.exe"
            VALUE "FileVersion", "3.1.6"
            VALUE "InternalName", "q"
            VALUE "OriginalFilename", "q.exe"
            VALUE "ProductName", "q"
            VALUE "ProductVersion", "3.1.6"
        END
    END

    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x0409, 1200
    END
END

Compiled the script, saved the .exe, and;

Code:
C:\...\q-text-as-data>echo %@verinfo[q.exe]
3.1.6

C:\...\q-text-as-data>fileinfo q.exe
C:\Program Files\q-text-as-data\q.exe

    “q.exe” — 3.1.6

    “Application”  (.EXE)

    35,680,256 bytes     34.03 megabytes

    Created:   Fri 2021-11-26  17:50:14
    Modified:  Fri 2021-11-26  17:50:14
    Accessed:  Wed 2026-05-20  14:38:38

    Attributes:  Archive
    File owner:  BUILTIN\Administrators

    CRC-32 :  88B3748E
    MD5    :  ECE9126E1CD29CE76DC1024B7D752B4E

    Executable type ......... Windows 64-bit (console)

    Product Name ............ q
    Internal Name ........... q
    Original Filename ....... q.exe
    Version (binary) ........ 3.1.6.0
    Product Version ......... 3.1.6
    File Version ............ 3.1.6
    Company Name ............ Your Company

1 matching file found.

Properties from Windows Explorer;

1779302516727.webp


Ref: Resource Hacker

Joe
 
If the versioninfo is not in the PE header,
@verinfo and FileInfo will not see the Version Info.

Code:
C:\...\q-text-as-data>sigcheck.exe q.exe

Sigcheck v2.91 - File version and signature viewer
Copyright (C) 2004-2026 Mark Russinovich
Sysinternals - www.sysinternals.com

C:\Program Files\q-text-as-data\q.exe:
        Verified:       Unsigned
        Link date:      1:50 PM 2021-11-26
        Publisher:      n/a
        Company:        Your Company
        Description:    q.exe
        Product:        q
        Prod version:   3.1.6
        File version:   3.1.6
        MachineType:    64-bit

PowerShell does not read the same thing TCC reads.

TCC reads ONLY the Win32 PE VERSIONINFO resource inside the PE resource table.

PowerShell does not read the PE resource table directly.

Instead, PowerShell uses .NET and will synthesize version info from:
The manifest
The file header
The assembly metadata
The FileVersion attribute (if present)
The ProductVersion attribute (if present)

Even if the EXE has no VERSIONINFO resource at all.

This is why PowerShell can show:
FileVersion
ProductVersion
CompanyName
FileDescription

…even when the EXE contains zero Win32 version resources.

Joe
 
I've updated my CsScript Plugin (attached) to include my own version of @VERINFO

If the PE VERSIONINFO resource is present,
that value is returned.

Otherwise it attempts to synthesize a version from ProductVersion,
.NET assembly version,
or other metadata.

This is similar to how Windows Explorer gets the info.

Code:
R:\test>test.btm BrickstreamDataCollector.exe
@verinfo is an internal variable
ECHO is OFF
@verinfo is a plugin variable (CsScript)
2, 1, 0, 2

test.btm
Code:
@SETLOCAL
@ECHO OFF
iff exist %1 then
  set theFile=%1
else
  echo %1 not found.
  quit
endiff

which @verinfo
echo %@verinfo[%theFile]

iff not plugin CsScript then
  iff exist CsScript.dll then
    plugin /l CsScript.dll
  else
    echo Could not find CsScript.dll
    quit
  endiff
endiff 

which @verinfo
echo %@verinfo[%theFile]
plugin /u CsScript.dll
ENDLOCAL

C# Code for the @VERINFO function in CsScript
Code:
        /// Variable function: @VERINFO[filePath]
        /// Returns a best-effort file version string. If the PE VERSIONINFO resource is
        /// present, that value is returned. Otherwise attempts to synthesize a version
        /// from ProductVersion, .NET assembly version, or other metadata.
        public int f_VERINFO(StringBuilder args)
        {
            string input = args.ToString().Trim();
            if (string.IsNullOrEmpty(input))
            {
                args.Clear();
                return 0;
            }

            string path = input;
            if (!Path.IsPathRooted(path))
                path = Path.Combine(Environment.CurrentDirectory, path);

            if (!File.Exists(path))
            {
                args.Clear();
                return 0;
            }

            try
            {
                // Prefer the Win32/PE resource values via FileVersionInfo (this also
                // lets .NET synthesize values from manifests/assembly attributes).
                var fvi = FileVersionInfo.GetVersionInfo(path);
                string ver = fvi.FileVersion;
                if (string.IsNullOrEmpty(ver))
                    ver = fvi.ProductVersion;

                if (!string.IsNullOrEmpty(ver))
                {
                    args.Clear();
                    args.Append(ver);
                    return 0;
                }

                // If not available, try .NET assembly header version.
                try
                {
                    var an = AssemblyName.GetAssemblyName(path);
                    if (an != null && an.Version != null)
                    {
                        args.Clear();
                        args.Append(an.Version.ToString());
                        return 0;
                    }
                }
                catch (BadImageFormatException) { /* Not a .NET assembly */ }
                catch { /* ignore other failures */ }

                // No version information found - return empty string (consistent with TCC behavior).
                args.Clear();
                return 0;
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"f_VERINFO error: {ex.Message}");
                args.Clear();
                return 0;
            }
        }

This is meant to be a proof-of-concept to obtain version info if the PE Header does not contain version info.

Let me know how your testing goes.

Joe
 

Attachments

.NET plugins are not available in TCC v35

You need at least TCC v36.00.16 for .NET plugins to work with TCC.

Joe
 
Thank you. That works fine.
Is this code likely to make it into the main TCC build in a future version, or should I continue to use this plugin indefinitely?
 
That depends when @rconn can incorporate the changes into TCC using C++

Note well that my @verinfo only accepts the filename parameter.

My @verinfo does not accept info or language.

Joe
 
Thank you. That works fine.
Is this code likely to make it into the main TCC build in a future version, or should I continue to use this plugin indefinitely?
You could also create your own @VerInfo function;
Code:
function VerInfo=`%@pshell[(get-item %@truename[%1]).VersionInfo.FileVersion]`

which @verinfo
@verinfo is a user-defined variable function (%@pshell[(get-item %@truename[%1]).VersionInfo.FileVersion])

echo %@verinfo[csscript.dll]
2026.5.21.0

echo %@verinfo[BrickstreamDataCollector.exe]
2, 1, 0, 2

Joe
 
Last edited:
I've noticed for a very long time that @verinfo will sometimes show very different information from what Windows will show in the file details.

For me, it's important because if I'm building a software push using Configuration Manager, for example, and I want to have it detect a successful install by looking at the version info of whatever executable, there are times when an EXE shows a shorter version like 1.2 for the main "ProductVersion", but when I use Explorer and look at the file details, and what config manager actually detects, it's a longer 1.2.3.4 version.

It's rare, and I'm trying to remember which particular software would always do that... Oh, here's one. 7-Zip's executable.

echo %@verinfo[7z.exe,ProductVersion] -- %@verinfo[7z.exe,FileVersion]

Both return just "26.01". But if I relied on that for detection, it would be wrong, because it's detecting "FileVersion" which, according to the actual file details, is 26.1.0.0

There seems to be some nuance to how compilers store these kinds of version/build/copyright info. Powershell also just shows 26.01, sigcheck shows 26.01, but clear as day when you right-click the 7z.exe in Explorer and look at the details tab, 26.1.0.0 is staring at me for FileVersion.

Over the years there have been a few other programs that did this, and when building deployments I just remembered which ones I had to look at using explorer to get what version it'll return. I don't do as much of that now but it did always strike me as odd.

Another app I use that shows the real version 26.1.0.0 is Beyond Compare (a file/directory comparison tool). When comparing and showing version info in the list, it's showing me 26.1.0.0 for the version, so there's another app that is somehow able to get that version string from wherever.

Anyway, long story short, apparently there are different ways an app can store version info in an EXE. Go figure.

UPDATE and note to self, in Powershell I can do this to get the raw version info (as a version-type array):
(gi 7z.exe).VersionInfo.FileVersionRaw

Major Minor Build Revision
----- ----- ----- --------
26 1 0 0

Or this:
(gi 7z.exe).VersionInfo.FileVersionRaw.ToString()

Returns a pleasant 26.1.0.0 just like you'd expect to see.
 
Last edited:
The VERSIONINFO resource can contain a number of different versions, in different or even contradictory formats. The binary file version and the binary product version (usually the same, but not necessarily) are each interpreted as four 16-bit integers. But there are also version strings, which can be localized into different languages and which may contain whatever wacky stuff the programmer might fancy. Roman numerals... numbers spelled out... numbers spelled out in Russian... two parts, four parts, six parts, leading zeros, leading spaces... anything.

The @VERINFO function returns the strings. I do have a plugin function to return the binary file version: @FILEVER
 
The VERSIONINFO resource can contain a number of different versions, in different or even contradictory formats. The binary file version and the binary product version (usually the same, but not necessarily) are each interpreted as four 16-bit integers. But there are also version strings, which can be localized into different languages and which may contain whatever wacky stuff the programmer might fancy. Roman numerals... numbers spelled out... numbers spelled out in Russian... two parts, four parts, six parts, leading zeros, leading spaces... anything.

The @VERINFO function returns the strings. I do have a plugin function to return the binary file version: @FILEVER

Oh, interesting, I'll have to grab that plugin.

I suppose it would be preferable, or at least nice-to-have, if @verinfo returned the raw version info instead of the strings, perhaps as a similar FileVersionRaw, ProductVersionRaw that Powershell uses.

I have an alias I define called "filever" that looks like this, so I can easily grab all the details (helps me with my work):
alias filever=echo Filename: %$^r^nAttrib: %@attrib[%$]^r^nSize: %@comma[%@filesize[%$]] bytes^r^nDate: %@filedate[%$,,4]^r^n---------------- & for %%verinfo in (ProductVersion FileVersion Build CompanyName FileDescription InternalName LegalCopyright LegalTrademarks OriginalFilename ProductName) echo %verinfo%: "%@verinfo[%$,%verinfo%]"

Bit of a mouthful there but spits out all the fields that @verinfo can provide along with the attributes, date, size.
 
I suppose it would be preferable, or at least nice-to-have, if @verinfo returned the raw version info instead of the strings, perhaps as a similar FileVersionRaw, ProductVersionRaw that Powershell uses.

My thought would be to overload the "language" parameter. If it's -1, return the binary file info. Ought to be backwards-compatible, no?
 
I just made another alias called "psfilever" that looks like this, and grabs extended info using Powershell. :)

echo Filename: %$^r^nAttrib: %@attrib[%$]^r^nSize: %@comma[%@filesize[%$]] bytes^r^nDate: %@filedate[%$,,4]^r^n---------------- & powershell -command "& {(gi %$).VersionInfo | fl -property *}"

Shows me that same kind of basic info upfront but gets all the other goodies using the powershell get-item details.

EDIT: Oh, well that's embarrassing, I forgot about the @pshell function, and that would be much simpler than calling it with powershell -command. I ran into double-quote issues if the filename I passed to my alias had spaces... anyway, ended up with this for an ALIAS:
psfilever=echo Filename: %$^r^nAttrib: %@attrib[%$]^r^nSize: %@comma[%@filesize[%$]] bytes^r^nDate: %@filedate[%$,,4]^r^n----------------^r^n%@pshell[(gi %$).VersionInfo | fl -property *]

Runs faster too.
 
Last edited:
Final version I wound up with after running into issues with UNC paths, quotes or not around things, etc:
psfilever=echo Filename: %$^r^nAttrib: %@attrib[%$]^r^nSize: %@comma[%@filesize[%$]] bytes^r^nDate: %@filedate[%$,,4]^r^n----------------^r^n%@pshell[(gi %@quote[%@full[%$]]).VersionInfo | fl -property *]

I could "fix" all the other things where it's using %$ to get the parameter (optional path + file name) but I've never really had issues since I'm double quoting or not as I go with tab expansion. But the @pshell was being especially picky about it. If my CWD was a UNC path then it would especially fail, like looking at "code.exe" on a remote server's program files directory for example. Using the full UNC path is fine, but there could be inconsistencies on whether the path and the filename were double quoted, etc. and it was just weird. :) Using @full to get the actual path, and then @quote to add double-quotes around it if needed... well, that seems to be working. I suppose I probably should set the filename to that at the start of my alias and use that consistently throughout but you get the idea.

All that just to have a quick one-liner to get the actual raw file version info, but it's worth it.
 
Back
Top