How to edit a MST file using PowerShell
Hi there,
I am trying to edit a MST file by script but I am bit stucked. So far, this is my code
[Code]
function Modify-MSI
{
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[ValidateScript({$_ | Test-Path -PathType Leaf})]
[string]$MSI_Path,
[Parameter(Mandatory=$false)]
[string]$MST_Path
)
$installer = New-Object -ComObject WindowsInstaller.Installer
#$database = Invoke-Method $installer OpenDatabase @($MSI_Path, $msiViewModifyUpdate)
if (Test-Path $MST_Path )
{
#DB.ApplyTransform MSTFileName, 63
#[System.Windows.Forms.MessageBox]::Show($MST_Path)
$database = Invoke-Method $installer OpenDatabase @($MSI_Path, $msiOpenDatabaseModeTransact)
Invoke-Method $database ApplyTransform @($MST_Path, 1)
}
$view = Invoke-Method $database OpenView @("UPDATE Property SET Value='1' WHERE Property='ALLUSERS'")
Invoke-Method $view Execute
#$view = $database.OpenView("INSERT INTO Property (Property, Value) VALUES ('test','546)")
#$View = $Database.GetType().InvokeMember(“OpenView”, “InvokeMethod”, $Null, $Database, ("INSERT INTO Property (Property, Value) VALUES ('test','546)"))
#Invoke-Method $view Execute
Invoke-Method $view Close @()
$view = $null
$database = $null
}
[/Code]
In this example I only want to update the ALLUSERS but in future I also would like add/change more stuff. As far as I could read in the internet I can not edit a MST file directly so I have to compare two MSI files and out if them get the MST.
Has anyone experience with it and could me help out?
Cheers
Stephan
Answers (7)
Try this that I cobbled together.....
try {
$msiOpenDatabaseModeReadOnly = 0
$msiTransformErrorNone = 0
$msiTransformValidationNone = 0
$database1Path = "c:\test\before.msi"
$database2Path = "c:\test\after.msi"
$MSTPath = "c:\test\difference.mst" $windowsInstaller = New-Object -com WindowsInstaller.Installer $database = $windowsInstaller.GetType().InvokeMember(
"OpenDatabase", "InvokeMethod", $Null,
$windowsInstaller, @($database1Path, $msiOpenDatabaseModeReadOnly)
) $database1 = $windowsInstaller.GetType().InvokeMember(
"OpenDatabase", "InvokeMethod", $Null,
$windowsInstaller, @($database2Path, $msiOpenDatabaseModeReadOnly)
)
$transformSuccess = $database1.GetType().InvokeMember(
"GenerateTransform", "InvokeMethod", $Null,
$database1, @($database,$MSTPath)
) $transformSummarySuccess = $database1.GetType().InvokeMember(
"CreateTransformSummaryInfo", "InvokeMethod", $Null,
$database1, @($database,$MSTPath, $msiTransformErrorNone, $msiTransformValidationNone)
) Write-Host "Created transform: $MSTPath"
}
catch
{
throw "Error creating Transform: {0}." -f $_
}
Have you tried Orca for your MSI/MST needs?
http://msdn.microsoft.com/en-us/library/windows/desktop/aa370557(v=vs.85).aspx
It is a Microsoft product so it's fairly safe, but it kind of a poor app. But it does what you want without a script.
Using Orca, you open the MSI, do "New Transform", make whatever changes you want to lines on the table, then save the "transform" as your MST, which overlays onto the MSI install (via command line call). E.G. msiexec /i test.msi /j /t test.mst...
You might search around on here for more information about Orca. But I like that script.
Hi Captain_Planet,
another question. Can u tell me whats the different between this VBS and this PowerShell Script. Btw. the PS is not working ;)
Dim oInstaller : Set oInstaller = CreateObject("WindowsInstaller.Installer") 'open the MSI (the first argument supplied to the vbscript) Dim oDatabase : Set oDatabase = oInstaller.OpenDatabase("C:\Users\shadmin\Desktop\7z920-x64.msi",0) If oDatabase.TablePersistent("Registry") = 1 Then msgbox "exist" end if
$installer = New-Object -ComObject WindowsInstaller.Installer $MSI_file2 = 'C:\Users\shadmin\Desktop\7z920-x64.msi' $database2 = $Installer.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $Null, $Installer, @(($MSI_file2), 0)) if (($database2.GetType().InvokeMember("TablePersistent", "InvokeMethod", $Null, $database2, 'Registry')) -eq 1) { write-host 'exist' }
Hi. Look here for a similar VBScript example:
http://www.alkanesolutions.co.uk/vbscript_windowsinstaller_tutorials.aspx#generatetransform
In summary, you need to:
- - take a copy of the original base MSI, and create a new temporary MSI
- - apply your transform to the temporary MSI, and any changes
- - find the difference between the temporary MSI (with applied transform) and the base (original) MSI
- - generate a transform from the differences between the base MSI and the temporary MSI.
So the two important methods you're missing are: GenerateTransform and CreateTransformSummaryInfo
Thx Captain Planet,
I also saw this on this "Internet" but now I am stucking with the Generate Transform method because it is not generating anything. So When I use the VBS its works, but not out of the PS ... the ALLUSERS property is in both MSI different which it generates just fine...
Invoke-Method $database3 GenerateTransform $database1 $transform
function Invoke-Method ($Object, $MethodName, $ArgumentList) { return $Object.GetType().InvokeMember($MethodName, 'Public, Instance, InvokeMethod', $null, $Object, $ArgumentList) }
maybe I should show the complete function, but its not finished yet, so maybe the transform part doesnt make sense yet, just be focused on the Generate Transform part ;)
function Modify-MSI { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [ValidateScript({$_ | Test-Path -PathType Leaf})] [string]$MSI_Path, [Parameter(Mandatory=$false)] [string]$MST_Path ) $installer = New-Object -ComObject WindowsInstaller.Installer #$database = Invoke-Method $installer OpenDatabase @($MSI_Path, $msiViewModifyUpdate) if ([System.IO.Path]::GetExtension($MSI_Path) -eq ".msi") { $MSI_file2 = [System.IO.Path]::GetFileNameWithoutExtension($MSI_Path) + '.001' + [System.IO.Path]::GetExtension($MSI_Path) Copy-Item $MSI_Path ($scriptpath + '\' + $MSI_file2) } if (Test-Path $MST_Path ) { #DB.ApplyTransform MSTFileName, 63 #[System.Windows.Forms.MessageBox]::Show($MST_Path) #Invoke-Method $database ApplyTransform @($MST_Path, 1) $transform = $MST_Path } Else { $transform = $scriptpath + '\' + [System.IO.Path]::GetFileNameWithoutExtension($MSI_Path) + '.mst' } $database1 = Invoke-Method $installer OpenDatabase @($MSI_Path, $msiOpenDatabaseModeReadOnly) $database2 = Invoke-Method $installer OpenDatabase @($MSI_file2, $msiViewModifyUpdate) $view = Invoke-Method $database2 OpenView @("UPDATE Property SET Value='1' WHERE Property='ALLUSERS'") Invoke-Method $view Execute #$view = $database.OpenView("INSERT INTO Property (Property, Value) VALUES ('test','546)") #$View = $Database.GetType().InvokeMember(“OpenView”, “InvokeMethod”, $Null, $Database, ("INSERT INTO Property (Property, Value) VALUES ('test','546)")) Invoke-Method $database2 Commit Invoke-Method $view Execute Invoke-Method $view Close @() $view = $null $database2 = $null if ([System.IO.Path]::GetExtension($MSI_Path) -eq ".msi") { $MSI_file3 = [System.IO.Path]::GetFileNameWithoutExtension($MSI_Path) + '.002' + [System.IO.Path]::GetExtension($MSI_Path) Copy-Item $MSI_file2 ($scriptpath + '\' + $MSI_file3) } $database3 = Invoke-Method $installer OpenDatabase @($MSI_file3, $msiOpenDatabaseModeReadOnly) # $result = $database3.GetType().InvokeMember("GenerateTransform", 'Public, Instance, InvokeMethod', $null, $Object, @($database1, $MST_Path)) #Invoke-Method $database3 GenerateTransform $database1 $transform #Invoke-Method $database3 GenerateTransform $database1 $transform #Invoke-Method $database3 CreateTransformSummaryInfo $database1 $transform 0 0 $database3.GetType().InvokeMember("GenerateTransform",[System.Reflection.BindingFlags]::InvokeMethod, $Null, $database3, @($database1, $transform)) #$database3.GetType().InvokeMember("GenerateTransform","InvokeMethod", $Null, $database3, @($database1, $transform)) #$View.GetType().InvokeMember("Execute", "InvokeMethod", $Null, $View, $Null) $errorConditions = 0 $validation = 2 + 8 + 256 # (2) msiTransformValidationProduct + (8) msiTransformValidationMajorVer + (256) msiTransformValidationEqual #Invoke-Method $database3 CreateTransformSummaryInfo $database1 $transform $errorConditions $validation [System.Runtime.Interopservices.Marshal]::ReleaseComObject($installer) $database1 = $null $database3 = $null }
Thanks for that. The mistake was how I opened the MSI, so it is better do it as u suggested with the real InvokeMember.
I guess wtithout u I never had coult find an answer
Have a nice weekend
Comments:
-
You're welcome. Don't forget to mark it as answered if it helped you. Have a good weekend too. - captain_planet 11 years ago
Ok, so far I could finish the script. Just one small problem left. Sometime the copy which I create want be released by powershell until I close it. Any ideas how I can do this?
Clear-Host $scriptpath = Split-Path -parent $MyInvocation.MyCommand.Definition function Invoke-Method ($Object, $MethodName, $ArgumentList) { return $Object.GetType().InvokeMember($MethodName, 'Public, Instance, InvokeMethod', $null, $Object, $ArgumentList) } $msiOpenDatabaseModeReadOnly = 0 $msiOpenDatabaseModeTransact = 1 $msiViewModifyUpdate = 2 $msiViewModifyReplace = 4 $msiViewModifyDelete = 6 $msiTransformErrorNone = 0 $msiTransformValidationNone = 0 Function main { [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | out-null $WinForm = new-object Windows.Forms.Form $WinForm.text = "MSI Modifier" $WinForm.Size = new-object Drawing.Size(400,220) $objTextBox1 = New-Object System.Windows.Forms.TextBox $objTextBox1.Location = New-Object System.Drawing.Size(10,20) $objTextBox1.Size = New-Object System.Drawing.Size(260,20) $winform.Controls.Add($objTextBox1) if ($objTextBox1.Text -ne $null) {$objTextBox1.Text = "MSI-File or ISM-File"} $objTextBox2 = New-Object System.Windows.Forms.TextBox $objTextBox2.Location = New-Object System.Drawing.Size(10,60) $objTextBox2.Size = New-Object System.Drawing.Size(260,20) $winform.Controls.Add($objTextBox2) $objTextBox2.Text = "MST-File" $SelectButton1 = New-Object System.Windows.Forms.Button $SelectButton1.Location = New-Object System.Drawing.Size(280,18) $SelectButton1.Size = New-Object System.Drawing.Size(75,23) $SelectButton1.Text = "Select" $SelectButton1.Add_Click({$objTextBox1.Text = Select-FileDialog}) #$SelectButton1.Add_Click((if({$objTextBox1.Text = Select-FileDialog} -eq "") {$objTextBox1.Text = "MSI-File"} )) #$objTextBox1.Text = if($SelectButton1.Add_Click({$objTextBox1.Text = Select-FileDialog})-ne $null) {$objTextBox1.Text = "MSI-File"} $winform.Controls.Add($SelectButton1) $SelectButton2 = New-Object System.Windows.Forms.Button $SelectButton2.Location = New-Object System.Drawing.Size(280,58) $SelectButton2.Size = New-Object System.Drawing.Size(75,23) $SelectButton2.Text = "Select" $SelectButton2.Add_Click({$objTextBox2.Text = Select-FileDialog}) $winform.Controls.Add($SelectButton2) $ModifyButton = New-Object System.Windows.Forms.Button $ModifyButton.Location = New-Object System.Drawing.Size(80,110) $ModifyButton.Size = New-Object System.Drawing.Size(75,23) $ModifyButton.Text = "Modify" $ModifyButton.Add_Click({Modify-MSI -MSI_Path $objTextBox1.Text -MST_Path $objTextBox2.Text}) $winform.Controls.Add($ModifyButton) $CloseButton = New-Object System.Windows.Forms.Button $CloseButton.Location = New-Object System.Drawing.Size(280,110) $CloseButton.Size = New-Object System.Drawing.Size(75,23) $CloseButton.Text = "Close" $CloseButton.Add_Click({$WinForm.Close()}) $winform.Controls.Add($CloseButton) $ResultLabel = New-Object System.Windows.Forms.Label $ResultLabel.Location = New-Object System.Drawing.Size(10,140) $ResultLabel.Size = New-Object System.Drawing.Size(380,160) $ResultLabel.Text = "Result: " $winform.Controls.Add($ResultLabel) $WinForm.Add_Shown($WinForm.Activate()) $WinForm.showdialog() | out-null #$ListBox.SelectedItem } function Select-FileDialog { param([string]$Title,[string]$Directory,[string]$Filter="MSI/MST/ISM Files (*.msi,*.mst,*.ism)|*.msi;*.mst;*.ism") [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null $objForm = New-Object System.Windows.Forms.OpenFileDialog $objForm.InitialDirectory = $Directory $objForm.Filter = $Filter $objForm.Title = $Title $objForm.ShowHelp = $true $Show = $objForm.ShowDialog() If ($Show -eq "OK") { Return $objForm.FileName } Else { Return "Operation cancelled by user" #Write-Error "Operation cancelled by user" } } function Change-MSIProperties { param ( $database2, [string]$str_propertyname, [string]$str_propertyvalue ) #$view = Invoke-Method $database2 OpenView @("SELECT * FROM Property WHERE Property='$str_propertyname'") $view = $database2.GetType().InvokeMember("Openview", "InvokeMethod", $Null, $database2, @("SELECT * FROM Property WHERE Property='$str_propertyname'")) Invoke-Method $view Execute $record = Invoke-Method $view Fetch @() $view = $null if ($record -ne $Null) { #$view = Invoke-Method $database2 OpenView @("UPDATE Property SET Value='$str_propertyvalue' WHERE Property='$str_propertyname'") $view = $database2.GetType().InvokeMember("Openview", "InvokeMethod", $Null, $database2, @("UPDATE Property SET Value='$str_propertyvalue' WHERE Property='$str_propertyname'")) } Else { #$view = Invoke-Method $database2 OpenView @("INSERT INTO Property (Property, Value) VALUES ('$str_propertyname','$str_propertyvalue')") $view = $database2.GetType().InvokeMember("Openview", "InvokeMethod", $Null, $database2, @("INSERT INTO Property (Property, Value) VALUES ('$str_propertyname','$str_propertyvalue')")) } Invoke-Method $view Execute Invoke-Method $view Close @() $view = $Null } function Modify-MSI { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [ValidateScript({$_ | Test-Path -PathType Leaf})] [string]$MSI_Path, [Parameter(Mandatory=$false)] [string]$MST_Path ) try { $installer = New-Object -ComObject WindowsInstaller.Installer $WorkingDir = $MSI_Path.TrimEnd([System.IO.Path]::GetFileName($MSI_Path)) #if ([System.IO.Path]::GetExtension($MSI_Path) -eq ".msi") if (Test-Path $MSI_Path ) { $MSI_file2 = [System.IO.Path]::GetFileNameWithoutExtension($MSI_Path) + '.001' + [System.IO.Path]::GetExtension($MSI_Path) Copy-Item $MSI_Path ($WorkingDir + $MSI_file2) } #$database1 = Invoke-Method $installer OpenDatabase @($MSI_Path, $msiOpenDatabaseModeReadOnly) $database1 = $Installer.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $Null, $Installer, @($MSI_Path, $msiOpenDatabaseModeReadOnly)) #$database2 = Invoke-Method $installer OpenDatabase @(($WorkingDir + $MSI_file2), $msiViewModifyUpdate) $database2 = $Installer.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $Null, $Installer, @(($WorkingDir + $MSI_file2), $msiViewModifyUpdate)) if (Test-Path $MST_Path ) { $transform = [System.IO.Path]::GetFileNameWithoutExtension($MST_Path) + '.new' + [System.IO.Path]::GetExtension($MST_Path) $database2.GetType().InvokeMember("ApplyTransform", "InvokeMethod", $Null, $database2, @($MST_Path, 63)) } Else { if ([System.IO.Path]::GetExtension($MSI_Path) -eq ".msi") { $transform = $WorkingDir + [System.IO.Path]::GetFileNameWithoutExtension($MSI_Path) + '.mst' } if ([System.IO.Path]::GetExtension($MSI_Path) -eq ".ism") { $transform = $Null } } Change-MSIProperties $database2 'ALLUSERS' '1' Change-MSIProperties $database2 'AgreeToLicense' 'Yes' Change-MSIProperties $database2 'REBOOT' 'ReallySuppress' Change-MSIProperties $database2 'RebootYesNo' 'No' Change-MSIProperties $database2 'Reinstallmode' 'OMUS' Invoke-Method $database2 Commit $database2 = $Null if ($transform -ne $Null) { if (Test-Path ($WorkingDir + $MSI_file2) ) { $MSI_file3 = [System.IO.Path]::GetFileNameWithoutExtension($MSI_Path) + '.002' + [System.IO.Path]::GetExtension($MSI_Path) Copy-Item ($WorkingDir + $MSI_file2) ($WorkingDir + $MSI_file3) } $database3 = $Installer.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $Null, $Installer, @(($WorkingDir + $MSI_file3), $msiOpenDatabaseModeReadOnly)) #Invoke-Method $database3 GenerateTransform $database1 $transform $database3.GetType().InvokeMember("GenerateTransform", "InvokeMethod", $Null, $database3, @($database1,$transform)) $transformSummarySuccess = $database3.GetType().InvokeMember("CreateTransformSummaryInfo", "InvokeMethod", $Null, $database3, @($database1,$transform, $msiTransformErrorNone, $msiTransformValidationNone)) } [System.Runtime.Interopservices.Marshal]::ReleaseComObject($installer) $database1 = $Null $database3 = $Null if ($transform -ne $Null) { $ResultLabel.Text = "Result: Created transform: $transform" } else { $transform = [System.IO.Path]::GetFileNameWithoutExtension($MSI_Path) + '.001' + [System.IO.Path]::GetExtension($MSI_Path) $ResultLabel.Text = "Result: Created ISM: $transform" } } catch { $ResultLabel.Text = "Result: Error creating Transform: {0}." -f $_ } $transform = $Null } main