一个完全由自己维护的打包项目再提交产品的时候因为自己的疏漏,忘记修改产品id了,导致两个有共存关系的产品呢,只能安装一个,不过所幸虽然已经交付,但是没有完全上线,而且这个问题是自己在测试其他功能的时候顺带发现的(PS:测试也没发现呢0_0),所以还算比较及时,把产品追回了,重新生成了一版提交上去了。
当然,这个事故也给我们整个研发测试团队敲了个警钟(PS:感谢老大,直接找产品部门把项目追回了,没有惊动技术总监,不然我怕是得挨训,闹不好KPI甚至职业生涯也要受影响,当然,测试那边估计也不好受,哈哈,吐槽有点多),言归正传,我专门写了正式邮件给我的经理(不是技术总监)以及测试的老大,有关这个事情以及给测试的建议。好了,测试一封回信我又给自己找了个新活(提供一种手段可以获得msi的productCode和UpgradeCode \0_0/)。happy!快乐!
首先我想到的是右键msi文件查查属性,没准直接有我想要的(印象中好像在属性里有看到类似的),
上bing搜一搜看咯,没准有现成的。当然,百度也同时在看。倒也有点收获:
一个来自CSDN的方法分享,使用记事本打开对应的msi文件,可以看到一堆乱码,其中也夹杂着一些可见数据,使用ctrl+f打开搜素栏,搜索ProductCode,会索引到对应的productCode,ProductName,则是对应的UpgradeCode。
可惜这个并不能满足我的要求,因为我需要能够将他输出出来,而不是由别人自己去找(其实也行,主要测试不乐意,嫌麻烦),那没办法,怎么搞呢,想想有没有其他的方法。不过从上面的例子我已经知晓这个所谓的productCode是打包之后也可见的,并不如我初期预测的只能在打包前在VS里面看到。既然这样的话,那是不是有现成的接口可以供我使用。所以我来到了MSDN,开始搜ProductCode和getProductCode等字眼.还真让我找到了,一个名为msi.dll的动态库。
UINT MsiGetProductCodeA(
LPCSTR szComponent,
LPSTR lpBuf39
);
get_ProductCode(string Component)
可以通过下面的函数获取ProductCode,但是,我并没有在这里找到UpgradeCode相关的函数,另一方面,因为没有这样玩过,所以搞了个小的用来测试的项目,打算写个小demo测试一把效果,但是我有点愁,貌似我没法直接断点去看(不讨论msi点击安装后附件到进程这种骚操作,即使这样,如果涉及创建子进程用完就杀那种也不好搞,之前调试另一个msi的时候就遇到这问题),那打log呗,写了点打log的函数,生成个日志文件,写点内容,生成安装包,当我真正把生成的安装包拿到我的测试虚机进行安装的时候,我发现事情貌似并不如我所料想的那么简单,开始时安装失败,调试了一会安装成功了,但是我想要的产品id并没有打印出来,不知道是调用函数有问题还是参数传入不正确,反正毫无反应。
没招啊,又开始了百度,突然发现一个帖子里提到使用powershell的方法去获取ProdutCode,仔细瞄了一眼发现和我需求完全一样,果断就抛弃了第二方案!
说到这,就不得不提接下来的主角——get-wmiobject,作为一个powershell命令他背负了太多太多,看看官方解释
翻译过来就是获取Windows Management Instrumentation(WMI)类的实例或有关可用类的信息。通俗的讲,这个搞了个对象出来,当然,搞对象得有类啊,那么,第二主角也将出场——Win32_Product,WMI类,代表由Windows Installer安装的产品。有了这两个东西,我们想要的东西基本就完全了,来看我们两个主角的第一次双剑合璧!
emmm,不小心暴露姓氏了,言归正传,上面的IdentifyingNumber就是我想要的ProductCode,林列出来的只是安装的msi的部分属性,还有好多没有显示出来,那么怎么搞到另一个我想要的UpgradeCode呢,没思路啊,我又开始孜孜不倦的看起了MSDN,一个用例吸引了我的注意力,原文链接在这咯-》点这里
Get-WmiObject -Query "select * from win32_service where name='WinRM'" -ComputerName Server01, Server02 | Format-List -Property PSComputerName, Name, ExitCode, Name, ProcessID, StartMode, State, Status
PSComputerName : SERVER01
Name : WinRM
ExitCode : 0
Name : WinRM
ProcessID : 844
StartMode : Auto
State : Running
Status : OK
PSComputerName : SERVER02
Name : WinRM
ExitCode : 0
Name : WinRM
ProcessID : 932
StartMode : Auto
State : Running
Status : OK
这不就是索引条件嘛,瞌睡有人送枕头,可是这玩意怎么写呢,我又开始了自己的上网淘宝,有货!
gwmi -Query "SELECT ProductCode,Value FROM Win32_Property WHERE Property='UpgradeCode'" | Format-Table ProductCode,Value
舒服啊,一个索引,后面我加了格式化输出,将我想要的数据都打了出来,看看截图
到了这一步,其实已经基本解决问题了,但是还差个临门一脚,怎么把我想要的那个产品输出出来呢。
首先,我有个已经输出的表格,一个ProductCode对应一个UpgradeCode,然后我通过产品名字分辨出了我的msi的具体的ProductCode。这种情况下,加上几个变量,加个过滤条件,我觉着,应该就差不多解决问题了。程序员嘛,复制大师和拼接组装大师!下面就是我的完全体的脚本了。
$tmpCKBNmae = $args[0]
if (!$tmpCKBNmae)
{
Write-Host "error: No Parameter!"
return
}
$tmpList = get-wmiobject Win32_Product
foreach($tmp in $tmpList)
{
$testtmp = $tmp
if (!$testtmp.Name.ToString().CompareTo($tmpCKBNmae))
{
$ProductCode = $testtmp.IdentifyingNumber
break
}
}
$UpgradeCodeList = gwmi -Query "SELECT ProductCode,Value FROM Win32_Property WHERE Property='UpgradeCode'" | Format-Table ProductCode,Value
$UpgradeCodeList | findstr $ProductCode
通过一些条件的过滤,只需要在调用或者脚本的时候传入对应的msi名称,就可以将机器上已经安装的指定名字的MSI文件的productCode和UpgradeCode输出至命令行,完结散花!