/build/static/layout/Breadcrumb_cap_w.png

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


0 Comments   [ + ] Show comments

Answers (7)

Answer Summary:
Posted by: captain_planet 11 years ago
Black Belt
1

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 $_
}
Posted by: gcarpenter 11 years ago
Green Belt
0

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.

Posted by: mac-duff 11 years ago
Second Degree Blue Belt
0

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'
}
Posted by: captain_planet 11 years ago
Black Belt
0

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

Posted by: mac-duff 11 years ago
Second Degree Blue Belt
0

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
}
Posted by: mac-duff 11 years ago
Second Degree Blue Belt
0

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
Posted by: mac-duff 11 years ago
Second Degree Blue Belt
-1

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

 

 
 
This website uses cookies. By continuing to use this site and/or clicking the "Accept" button you are providing consent Quest Software and its affiliates do NOT sell the Personal Data you provide to us either when you register on our websites or when you do business with us. For more information about our Privacy Policy and our data protection efforts, please visit GDPR-HQ