I have a Windows MSI file, that I need to programmatically read the version number from. The only place I can see this version is within the Subject of the file details:
If I somehow can read the entire content of Subject this would be fine but is there any way to get this from PHP? The PHP is running in an IIS web server, if this helps ;-)
stat is of no help for this.
I considered doing a checksum of the file, and can do that, but I really need the real version.
For now I am unable to find a native PHP solution for this, so I have temporarily solved this by calling a Powershell script as it seems easier to do in there.
I am now having this PHP code:
$version = exec("powershell.exe -file GetMsiVersion.ps1 MyFile.msi);
With my above picture then $version will contain 1.5.9, so I will not even require to interpret the data from Subject.
The GetMsiVersion.ps1 Powershell script has this code:
function Get-Property ($Object, $PropertyName, [object[]]$ArgumentList) {
return $Object.GetType().InvokeMember($PropertyName, 'Public, Instance, GetProperty', $null, $Object, $ArgumentList)
}
function Invoke-Method ($Object, $MethodName, $ArgumentList) {
return $Object.GetType().InvokeMember($MethodName, 'Public, Instance, InvokeMethod', $null, $Object, $ArgumentList)
}
$Path = $args[0]
$msiOpenDatabaseModeReadOnly = 0
$Installer = New-Object -ComObject WindowsInstaller.Installer
$Database = Invoke-Method $Installer OpenDatabase #($Path, $msiOpenDatabaseModeReadOnly)
$View = Invoke-Method $Database OpenView #("SELECT Value FROM Property WHERE Property='ProductVersion'")
Invoke-Method $View Execute
$Record = Invoke-Method $View Fetch
if ($Record) {
Write-Output (Get-Property $Record StringData 1)
}
Invoke-Method $View Close #()
I will accept this as the best solution here-and-now but, I hope this can be archieved natively from PHP as I see this as a better and more clean solution - and by then I will accept that as the best answer (or at least unaccept my own temporary solution).
Doing an exec is a little evil ;-)
Two things first:
I have never accessed COM from PHP, but below are some VBScript samples of getting information from MSI files by using MSI API - COM automation. There are also Win32 functions.
That field you refer to is not a version field, but a text field from the MSI's "Summary Stream" - a special part of the MSI file with "meta information" of various kinds. Summary Information Stream (the full name).
Here is how you can get the REAL version of an MSI file. This is stored in the property "ProductVersion" in the MSI file. There are at least two different ways to retrieve it - by opening the MSI file as a session or just access the property table via SQL query:
Access version via Session object:
Const msiUILevelNone = 2
Dim installer : Set installer = CreateObject("WindowsInstaller.Installer")
installer.UILevel = msiUILevelNone
Set s = installer.OpenPackage("C:\MySetup.msi",1)
MsgBox CStr(s.ProductProperty("ProductVersion"))
Accessing version via SQL agains MSI database (property table):
Dim installer : Set installer = CreateObject("WindowsInstaller.Installer")
' Open MSI database in read-only mode (0)
Set db = installer.OpenDatabase("C:\MySetup.msi", 0)
Set view = db.OpenView("SELECT `Value` FROM `Property` WHERE `Property`='ProductVersion'")
view.Execute
Set record = view.Fetch
MsgBox CStr(record.StringData(1))
Then there is the issue of accessing the SummaryStream - which is what you are really asking by the looks of it - here is a simple smoke test with some hints as to what properties you can retrieve - be careful with the summary stream - corruption is possible in various ways (I don't recall the details, but it should be safe to access read-only):
Dim installer : Set installer = CreateObject("WindowsInstaller.Installer")
' Open MSI database in read-only mode (0)
Set db = installer.OpenDatabase("C:\MySetup.msi", 0)
MsgBox CStr(db.SummaryInformation.Property(3))
' 1 = "Codepage"
' 2 = "Title"
' 3 = "Subject"
' 4 = "Author"
' 5 = "Keywords"
' 6 = "Comments"
' 7 = "Template"
' 8 = "LastAuthor"
' 9 = "Revision"
' 11 = "Printed"
' 12 = "Created"
' 13 = "Saved"
' 14 = "Pages"
' 15 = "Words"
' 16 = "Characters"
' 18 = "Application"
' 19 = "Security"
Links:
Modify an MSI using MSI SQL VBScript WiRunSQL.vbs
List tables in MSI file using VBScript
Related
Preconditions:
Programming language: PHP.
Serialization system: Apache avro
PHP library: https://github.com/wikimedia/avro-php
I send various messages via Apache kafka, each message has its own structure (an array with a specific set of keys) and is sent to a strictly defined topic, the data itself is encoded and decoded using Apache avro.
The problem is that the schema itself is transmitted along with the data, which is redundant in my case (Highload), there is no point in this since the client (the consumer of messages from Kafka) owns the schemas for each message structure (one structure - one Kafka topic).
Apparently, the current package https://github.com/wikimedia/avro-php is not suitable.
I'm looking for a ready-made solution - Avro encoder / decoder on PHP, which will allow not sending the schema itself along with the data every time, but substituting it on the client side (it will be stored as a file and substituted depending on the topic). It will save disk space and network traffic.
example of usage current solution:
<?php
require_once('../lib/avro.php');
$schemaJson = <<<_JSON
{"name":"member",
"type":"record",
"fields":[{"name":"foo", "type":"int"},
{"name":"bar", "type":"string"}]}
_JSON;
$item1 = ['foo' => 123, 'bar' => 'ktwop'];
$itemsForSerializing = [$item1];
$avroSchemaForWriter = \AvroSchema::parse($schemaJson);
$writeAvroStringIO = new \AvroStringIO();
$avroIODatumWriter = new \AvroIODatumWriter($avroSchemaForWriter);
$avroDataIOWriter = new \AvroDataIOWriter($writeAvroStringIO, $avroIODatumWriter, $avroSchemaForWriter);
foreach ($itemsForSerializing as $itemForSerializing) {
$avroDataIOWriter->append($itemForSerializing);
}
$avroDataIOWriter->close();
$encodedString = $writeAvroStringIO->string();
echo $encodedString . PHP_EOL . PHP_EOL;
// ACTUAL OUTPUT:
/*
Objavro.codenullavro.schema�{"type":"record","name":"member","fields":[{"name":"foo","type":"int"},{"name":"bar","type":"string"}]} �k����N�*��1�V��ktwop�k����N�*��1�V�
*/
// EXPECTED OUTPUT: ktwop�k����N�*��1�V�
$readAvroStringIO = new \AvroStringIO($encodedString);
$avroDataIOReader = new \AvroDataIOReader(
$readAvroStringIO, new \AvroIODatumReader($avroSchemaForWriter, $avroSchemaForWriter) // HERE I WANT TO USE SCHEMA FROM FILE ON CLIENT SIDE
);
echo "from binary string:" . PHP_EOL;
foreach ($avroDataIOReader->data() as $dataItem) {
echo var_export($dataItem, true) . PHP_EOL;
}
//OUTPUT:
/*
from binary string:
array (
'foo' => 123,
'bar' => 'ktwop',
)
*/
This should do it.
$io = new AvroStringIO();
$writer = new AvroIODatumWriter($schema);
$encoder = new AvroIOBinaryEncoder($io);
$writer->write($data, $encoder);
$io->string();
I'm using the Google Sheets API to write some data into the sheets, but so far either I clear it all and write everything again or I write in new rows (which Sheets API does by default).
I am now writing a single column per run, but I need to get the first available column in the sheet, so I can pass it as the range of writing.
This is my code so far:
$sheet = new \Google_Service_Sheets($this->client);
$range = "'" . $sheetName . "'!" . $rangeArg . (strlen($rangeArg) == 2 ? '' : count($data) + 1000);
$response = $sheet->spreadsheets_values->get($this->sheetId, $range);
if (!$clear && $response && $response->values) {
$c = count($response->values);
$newRange = (intval(substr($rangeArg, 1, 1)) + $c);
$newRange = substr($rangeArg, 0, 1) . $newRange . substr($rangeArg, 2);
$range = "'" . $sheetName . "'!" . $newRange . (count($data) + 1000);
}
$options = ['valueInputOption' => 'RAW'];
if ($clear) {
$sheet->spreadsheets_values->clear($this->sheetId, $range, new \Google_Service_Sheets_ClearValuesRequest);
}
$body = new \Google_Service_Sheets_ValueRange(['values' => $data, 'majorDimension' => $columns ? 'COLUMNS' : 'ROWS']);
$ok = $sheet->spreadsheets_values->append($this->sheetId, $range, $body, $options);
}
I saw someone on the internet mentioning getLastColumn() as a function, but it's not available in my version of sheets API apparently or it's just not in this package.
google/apiclient v2.5.0
google/apiclient-services v0.138
google/auth v1.9.0
getLastColumn is an appscript method. Something like that is not available with the php client library is only going to give you google sheets api methods,
The only way to find out whats on the sheet is to buffer it and scan though as you are already doing. Unfortunately with PHP there is really no easy way of doing it.
$response = $service->spreadsheets_values->batchGet($spreadsheetId);
I dont use PHP, but I am 100% sure, that in Google API its closest things on PHP and Python, just syntax of languages would be different
So for get the first free column through Google Sheet API (using Python):
import httplib2
from googleapiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials
DOCUMENT_ID = '1ma8DG3IQJ377900iaFUPu9qXoThuP22buLgEILVfO1M'
def get_service_sacc():
creds_json = 'param-param-1344-fae81984f8a8.json'
scopes = ['https://www.googleapis.com/auth/spreadsheets']
creds_service = ServiceAccountCredentials.from_json_keyfile_name(creds_json, scopes).authorize(httplib2.Http())
return build('sheets', 'v4', http=creds_service)
sheet_values = get_service_sacc().spreadsheets().values()
_range = "A1:A9"
# or use _range = "Name of List!A1:A9", if u want to take info not from first list of sheets.
write = sheet_values.append(spreadsheetId=DOCUMENT_ID, range=_range, valueInputOption="USER_ENTERED",body={}).execute()
deal_id = str(write['updates']['updatedRange']).split('!')
little info about how get creds.json, if you wanna make thru this method (its from Service Account (https://cloud.google.com/iam/docs/service-accounts), create it, go to you Cloud Developer Console, create a some project, create service account, append it to project, and also after creation, click on three dots at actions (in account line), click Manage keys -> Add key -> Create new key -> json and end it, than download that json, and put it together in one directory of you project where u start you language logic.)
info about, how append works (https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/append)
I'm using php and new COM('word.application') to manipulate a .doc file (add and edit fields), I'm on a Windows Server 2012 R2 with apache 2.4.25 x64 and php 7.0.17 x64.
So far I made a simple code which work great, but I have an issue when using functions with multiple parameters like this one
$word->Documents[1]->Protect(3, false, 'mypassword', false, false);
I get the following error :
[Erreur] Impossible de lancer le connecteur Microsoft Office Word. : com_exception: Parameter 4: Le type ne correspond pas
Approx translation in english :
[Error] Impossible to laungh the Microsoft Office Word connector : com_exception: Parameter 4: type does not correspond
In VBA code like this it's working
ActiveDocument.Protect Type:=wdAllowOnlyFormFields, NoReset:=True
According to the doc found in VBA
t.com/en-us/library/office/aa220366(v=office.11).aspx
Parameters 2, 4 and 5 should be booleans, so I don't know why I'm getting this error. If i remove parameters 4 and 5, I get the same error but on parameter 2.
With some searches i looked at the object new VARIANT() but it didn't work too.
I have also the problem with $this->Documents1->SaveAs()
$fileName = "D:\\test.doc";
$this->Documents[1]->SaveAs($fileName);
It works.
According to the doc (https://msdn.microsoft.com/en-us/library/microsoft.office.tools.word.document.saveas(v=vs.120).aspx) I should be able to use a second parameter like this :
$fileName = "D:\\test.doc";
$fileFormat = 0;
$this->Documents[1]->SaveAs($fileName, $fileFormat);
Like this I have the error "Parameter 0: Le type ne correspond pas" (Type does not correspond).
In VBA it's working differently, maybe I'm doing something wrong ...
Much thanks if you help me :)
edit : full example code
<?php
$word = new COM("word.application") or die("Cannot create Word object");
echo "com object created<br>";
$word->Visible = false;
$word->WindowState = 2;
$word->DisplayAlerts = false;
$word->Documents->Open("D:\\test.doc");
echo "document loaded<br>";
$import = $word->Documents[1]->VBProject->VBComponents->Import("D:\\fr.mac");
$import->name = "Macrofr";
$myModule = $word->Documents[1]->VBProject->VBComponents->Import("D:\\test1.mac");
$myModule->name = "MacroInit";
$myModule2 = $word->Documents[1]->VBProject->VBComponents->Import("D:\\test2.mac");
$myModule2->name = "MacroFonction";
$myModule3 = $word->Documents[1]->VBProject->VBComponents->Import("D:\\test3.frm");
$myModule3->name = "MacroProgression";
$word->Documents[1]->Protect(3,false,'motdepasse',false,false); // (type de protection,noReset,mdp)
$word->Documents[1]->SaveAs("D:\\doc_with_macro.doc", 0);
$word->Documents[1]->Close();
echo "closing doc<br>";
$word->Quit();
$word = null;
echo "end<br>";
The only problem that I see in this code is related to protect function. First parameter must be VARIANT like this :
$word->Documents[1]->Protect(
new VARIANT(3, VT_I4),
false,
'Your password',
false,
true
);
I'm using the Solr PHP Client and have the Solr 4.3.0 example up and running. I have not modified the schema.xml file. When I run this code I get a 400 error:
Uncaught exception 'Apache_Solr_HttpTransportException' with message '400' Status: Bad Request.'
The document does not show up in the index. Interestingly, If I restart Jetty, the document is indexed. Here's my code. I was wondering if I'm missing something. I thought this was an issue with my input matching the schema, but id seems to be the only required field and these other fields are in the schema. I'm not sure what to do.
require_once('SolrPhpClient/Apache/Solr/Service.php');
$solr = new Apache_Solr_Service('localhost', 8983, '/solr/');
if($solr->ping() == null){
die('could not ping solr');
}
$document = new Apache_Solr_Document();
$document->id = 'guid1';
$document->title = 'Title1';
$document->subject = 'The subject is solr';
$document->description = 'This is the description';
$solr->addDocument($document);
$solr->commit();
The full error message I get is
Fatal error: Uncaught exception 'Apache_Solr_HttpTransportException' with message ''400' Status: Bad Request' in C:\xampp\htdocs\dev\SolrPhpClient\Apache\Solr\Service.php:364
Stack trace:
#0 C:\xampp\htdocs\dev\SolrPhpClient\Apache\Solr\Service.php(829): Apache_Solr_Service->_sendRawPost('http://localhos...', '<commit expunge...', 3600)
#1 C:\xampp\htdocs\dev\indexerSOLR_PHP.php(20): Apache_Solr_Service->commit()
#2 {main} thrown in C:\xampp\htdocs\dev\SolrPhpClient\Apache\Solr\Service.php on line 364`
This is a known issue with Solr 4.x and calling commit from the Solr PHP Client. Please see
Bug #62332 - As of solr 4.0 the waitFlush parameter is removed for commit for the details and a patch to fix the issue.
That is what how I got the solution, I modified commit method. Added '&commit=true' to the _updateurl variable.
public function commit($expungeDeletes = false, $waitFlush = true, $waitSearcher = true, $timeout = 3600)
{
$expungeValue = $expungeDeletes ? 'true' : 'false';
$flushValue = $waitFlush ? 'true' : 'false';
$searcherValue = $waitSearcher ? 'true' : 'false';
//$rawPost = '<commit expungeDeletes="' . $expungeValue . '" waitFlush="' . $flushValue . '" waitSearcher="' . $searcherValue . '" />';
//$this->post=$rawPost;
return $this->_sendRawGet($this->_updateUrl.'&commit=true', $timeout);
}
You have change this line.
require_once('SolrPhpClient/Apache/Solr/Service.php'); => require_once('Service.php');
Maybe Service.php this file's path wrong. I try to changed this line. Then work successfully.
I had the same issue because I have installed an old version of the PHP Solr extension (0.9.11).
To get a the latest one:
pecl download solr-beta
tar xvzf solr-2.1.0.tgz # This can be different depending on the last release number
cd solr-2.1.0/
phpize
./configure
make
sudo make install
# add extension=solr.so to your php.ini / distribution extension loader
Thanks to this post.
I have a list of MAC addresses coming from a database. I would like to lookup the vendor for each MAC address and then have a count of devices on network by vendor in the end.
I believe I could do it the dirty way which would be to parse the vendor prefixes from the file available here http://standards.ieee.org/develop/regauth/oui/oui.txt.
But I'm wondering if there is a better way ?
There is a library in Pear, but it does have substantial overhead involved in that the vendor lookup requires a relational database that's been loaded with the vendor data. However, considering the alternative this might be worth exploring.
http://pear.php.net/manual/en/package.networking.net-mac.php
The package provides a loader for the list maintained by wireshark: https://code.wireshark.org/review/gitweb?p=wireshark.git;a=blob_plain;f=manuf
If all you care about is getting the manufacturer of the device based on the mac address then you can simply copy and paste the list on this website here (unto 200 at a time). It's very quick:
www.admin-toolkit.com/mac-address-lookup-manufacturer.html
But I'm wondering if there is a better way ?
If you can use other languages like ruby, there's a gem called macvendors
There is no need to use an external api or get stopped by rate limit.
You can use it from your command line:
gem install macvendors
macvendors find 98:e0:d9:a5:61:eb
Apple, Inc.
You can use it in your ruby code:
require 'macvendors'
MacVendors.setup #for the first time
puts MacVendors.find("98:e0:d9:a5:61:eb")
I have made an SQlite (Macvendors.db) from the Wireshark manuf source. I use it via PDO with a simple query.
Here is a method I write to show you how you could use it:
public function getAllCompaniesFromMacs($macs)
{
try {
// connect to the SQLite file
$conn = new \PDO("sqlite:/path/to/sqlite/Macvendors.db");
$conn ->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
// aux vars
$macsParaBuscar = array();
$macsIN = "";
// output vars
$salida = array();
// Clean repeated MACs and remove the ":" chars
foreach ($macs as $unaMac) {
$unaMac = str_replace(":", "", $unaMac);
$firstChars = substr($unaMac, 0, 6);
$macsParaBuscar[$firstChars] = $firstChars;
}
// Create a IN statment for the WHERE
$macsIN = "( 'HOLA'";
foreach ($macsParaBuscar as $unaMacBuscar) {
$macsIN .= ", '" . $unaMacBuscar . "'";
}
$macsIN .= ")";
// Prepare and execute the query
$stm = $conn->prepare("SELECT mac, vendor FROM macvendor WHERE mac IN " . $macsIN);
$stm->execute();
// Put results in output var
while( $row = $stm->fetch() ) {
$auxMac = $row["mac"];
$salida[$auxMac] = $row["vendor"];
}
return $salida;
} catch (\Exception $e) {
throw new \Exception("Ha ocurrido un error", 1);
}
}
Sorry about the unbeauty example code.
Have fun!
Update
There is another library that doesn't depend on any API but the XML file database which is Cisco vendorMacs.xml
The package is hosted here
Yes here is one I wrote, it depends on the online API of Mac address vendor website
Here is the repository with examples