4

Windows EXE files have some metadata like CompanyName, FileVersion, InternalName, ProductName, OriginalFileName, ProductVersion, etc.

How can I extract such metadata from using PHP?

sepehr
  • 17,110
  • 7
  • 81
  • 119
  • That info is usually stored in the .dll file of the exe try searching for that – happymacarts Dec 19 '16 at 18:00
  • No, it´s stored inside the exe file and I can extract these infos with C# and C++. My question is how to read these information from PHP. – danielfcand Dec 19 '16 at 18:36
  • not sure maybe check [this post](http://stackoverflow.com/questions/2479402/calling-c-c-library-function-from-php) – happymacarts Dec 19 '16 at 18:39
  • 1
    Upvoted this because this is a rather interesting question and while I might not ever need it, should the day come when I might, would be sweet to know the answer! I do not have it installed on my machine so I cannot test any of it, but does anybody know if anything with [File System Related Extensions](http://php.net/manual/en/refs.fileprocess.file.php) might be able to accomplish this? – Abela Dec 19 '16 at 20:06
  • 2
    Have you seen this? [Get Version of exe via PHP](https://stackoverflow.com/questions/2029409/get-version-of-exe-via-php). – sepehr Dec 20 '16 at 13:04
  • Yes, this is what I need. – danielfcand Dec 20 '16 at 17:08
  • Yes, this is what I need. The exe version is working weel, but still doesn't work to other properties like ProductVersion, ProductName, CompanyName, but this the way. Thanks a lot. – danielfcand Dec 20 '16 at 17:11

2 Answers2

7

I got curious about this, so I decided to write this function:

function getFileVersionInfo($filename,$encoding='UTF-8'){
    $dat = file_get_contents($filename);
    if($pos=strpos($dat,mb_convert_encoding('VS_VERSION_INFO','UTF-16LE'))){
        $pos-= 6;
        $six = unpack('v*',substr($dat,$pos,6));
        $dat = substr($dat,$pos,$six[1]);
        if($pos=strpos($dat,mb_convert_encoding('StringFileInfo','UTF-16LE'))){
            $pos+= 54;
            $res = [];
            $six = unpack('v*',substr($dat,$pos,6));
            while($six[2]){
                $nul = strpos($dat,"\0\0\0",$pos+6)+1;
                $key = mb_convert_encoding(substr($dat,$pos+6,$nul-$pos-6),$encoding,'UTF-16LE');
                $val = mb_convert_encoding(substr($dat,ceil(($nul+2)/4)*4,$six[2]*2-2),$encoding,'UTF-16LE');
                $res[$key] = $val;
                $pos+= ceil($six[1]/4)*4;
                $six = unpack('v*',substr($dat,$pos,6));
            }
            return $res;
        }
    }
}

It works with 32-bit and 64-bit exe. Usage example:

echo "<pre>".print_r(getFileVersionInfo('notepad.exe'),1)."</pre>";
echo "<pre>".print_r(getFileVersionInfo('php.exe'),1)."</pre>";
echo "<pre>".print_r(getFileVersionInfo('jre-7u9-windows-x64.exe'),1)."</pre>";

notepad.exe (32-bit):

Array
(
    [CompanyName] => Microsoft Corporation
    [FileDescription] => Notepad
    [FileVersion] => 6.1.7600.16385 (win7_rtm.090713-1255)
    [InternalName] => Notepad
    [LegalCopyright] => © Microsoft Corporation. All rights reserved.
    [OriginalFilename] => NOTEPAD.EXE
    [ProductName] => Microsoft® Windows® Operating System
    [ProductVersion] => 6.1.7600.16385
)

php.exe (32-bit):

Array
(
    [Comments] => Thanks to Edin Kadribasic, Marcus Boerger, Johannes Schlueter, Moriyoshi Koizumi, Xinchen Hui
    [CompanyName] => The PHP Group
    [FileDescription] => CLI
    [FileVersion] => 7.0.12
    [InternalName] => CLI SAPI
    [LegalCopyright] => Copyright © 1997-2016 The PHP Group
    [LegalTrademarks] => PHP
    [OriginalFilename] => php.exe
    [ProductName] => PHP
    [ProductVersion] => 7.0.12
    [URL] => http://www.php.net
)

jre-7u9-windows-x64.exe (64-bit):

Array
(
    [CompanyName] => Oracle Corporation
    [FileDescription] => Java(TM) Platform SE binary
    [FileVersion] => 7.0.90.5
    [Full Version] => 1.7.0_09-b05
    [InternalName] => Setup Launcher
    [LegalCopyright] => Copyright © 2012
    [OriginalFilename] => jinstall.exe
    [ProductName] => Java(TM) Platform SE 7 U9
    [ProductVersion] => 7.0.90.5
)

Something interesting about php.exe: the Comments and URL don't show up in the Details tab. At least in my computer.

Enjoy.

Update 1: I forgot error checking. Now it returns null if the version info doesn't exist.

Update 2: Many thanks to @Abela for bringing an encoding issue to my attention.

I added an optional 2nd parameter that defaults to UTF-8 which should work for most purposes. If you need single-byte-character output, use ISO-8859-1 instead, like this:

getFileVersionInfo('php.exe','ISO-8859-1');
Community
  • 1
  • 1
Rei
  • 6,263
  • 14
  • 28
  • 1
    @ray-rardin -- nice job mate, I can [confirm](https://i.stack.imgur.com/NCNev.png) that it is working on my end *(windows 2012 64bit server)* – Abela Dec 21 '16 at 02:55
  • 1
    @Abela Thank you for the confirmation and the screenshot. I noticed the copyright symbol there. I probably should change the ISO-8859-1 to UTF-8. – Rei Dec 21 '16 at 03:54
  • 1
    yeah, I actually noticed that *after* taking and upload the screenshot. I switched the code right afterwards to using `UTF-8` and it resolved it, I just did not feel like uploading a new image. Now just to see if @danielfcand is going to accept this as the answer ;) – Abela Dec 21 '16 at 04:08
  • @Rei great answer. I have a sugestion, limit `file_get_contents`, example: `file_get_contents($filename, false, null, 0, 15728640);`. **Note:** 15728640 = 1024 * 1024 *15. I tried different limits with different file sizes, 15728640 work fine for all tests, smaller than this caused some to return "null"... This suggestion is to prevent the script from dying if the executable is too large and exceeds the memory in the request, which can also help with the response time. ... My **"full suggestion"**: https://gist.github.com/brcontainer/24366009821b46862f1dfebc15b30a30 – Protomen Sep 20 '17 at 21:42
  • @GuilhermeNascimento Memory use can be an issue but limiting the size is not the solution. It seems you already know why. The proper solution is reading the file in chunks, say, 1 MB at a time. That way, it will be able to handle files of any size and memory use will stay low. – Rei Sep 21 '17 at 17:34
  • @Rei really, it would be much better :) ... It (file_get_contents $limit)still cause problems, then maybe I try "foef", anyway your script is the best I found, thanks :D – Protomen Sep 21 '17 at 17:39
  • @Rei, I have an EXE that throws an error when it returns $six = array(0) at the end of the while loop. I noticed that for EXEs that work, $six[2] returns 0, but $six[1] and $six[3] still have values. Can explain what the indexs refer to? I can tell 1 - length, what is 2 and 3? Thanks! – bilogic Mar 19 '19 at 03:06
0

This is what works for me. Works with .dll-s too.

function fsubstr($file_handle,$start,$lenght)
{
    fseek($file_handle,$start);
    $result = fread($file_handle,$lenght);
    return $result;
}

function fGetFileVersion($FileName)
{
    $handle = fopen($FileName, 'rb');
    if(!$handle)
    {
        return FALSE;
    }
    $Header = fread($handle, 64);
    if(substr($Header, 0, 2) != 'MZ')
    {
        return FALSE;
    }
    $PEOffset = unpack("V", substr($Header, 60, 4));
    if($PEOffset[1] < 64)
    {
        return FALSE;        
    }
    fseek($handle, $PEOffset[1], SEEK_SET);
    $Header = fread($handle, 24);
    if(substr($Header, 0, 2) != 'PE')
    {
        return FALSE;
    }
    $Machine = unpack("v", substr($Header, 4, 2));
    if($Machine[1] != 332)
    {        
        return FALSE;
    }
    $NoSections = unpack("v", substr($Header, 6, 2));
    $OptHdrSize = unpack("v", substr($Header, 20, 2));
    fseek($handle, $OptHdrSize[1], SEEK_CUR);
    $ResFound = FALSE;
    for ($x = 0; $x < $NoSections[1]; $x++)
    {
        $SecHdr = fread($handle, 40);
        if (substr($SecHdr, 0, 5) == '.rsrc')
        {
            $ResFound = TRUE;
            break;
        }
    }
    if(!$ResFound)
    {        
    return FALSE;
    }
    $InfoVirt = unpack("V", substr($SecHdr, 12, 4));
    $InfoSize = unpack("V", substr($SecHdr, 16, 4));
    $InfoOff = unpack("V", substr($SecHdr, 20, 4));
    $offset = $InfoOff[1];
    $NumDirs = unpack("v", fsubstr($handle, $offset + 14, 2));
    $InfoFound = FALSE;
    for ($x = 0; $x <$NumDirs[1]; $x++)
    {
        $Type = unpack("V", fsubstr($handle, $offset + ($x * 8) + 16, 4));
        if($Type[1] == 16)
        {
            //FILEINFO resource
            $InfoFound = TRUE;
            $SubOff = unpack("V", fsubstr($handle, $offset + ($x * 8) + 20, 4));
            break;
        }
    }
    if (!$InfoFound)
    {        
        return FALSE;
    }
    $SubOff[1] &= 0x7fffffff;
    $InfoOff = unpack("V", fsubstr($handle, $offset + $SubOff[1] + 20, 4)); //offset of first FILEINFO
    $InfoOff[1] &= 0x7fffffff;
    $InfoOff = unpack("V", fsubstr($handle, $offset + $InfoOff[1] + 20, 4));    //offset to data
    $DataOff = unpack("V", fsubstr($handle, $offset + $InfoOff[1], 4));
    $DataSize = unpack("V", fsubstr($handle, $offset + $InfoOff[1] + 4, 4));
    $CodePage = unpack("V", fsubstr($handle, $offset + $InfoOff[1] + 8, 4));
    $DataOff[1] -= $InfoVirt[1];
    $Version = unpack("v4", fsubstr($handle, $offset + $DataOff[1] + 48, 8));
    return sprintf("%u.%u.%u.%u",$Version[2],$Version[1],$Version[4],$Version[3]);
}
troll-e
  • 21
  • 3