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

Answer Summary:
Cancel
0 Comments   [ + ] Show Comments

Comments

Please log in to comment

Community Chosen Answer

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 $_
}
Answered 11/30/2012 by: captain_planet
Second Degree Brown Belt

Please log in to comment

Answers

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.

Answered 11/28/2012 by: gcarpenter
Green Belt

Please log in to comment
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'
}
Answered 12/20/2012 by: mac-duff
Second Degree Blue Belt

Please log in to comment
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

Answered 11/29/2012 by: captain_planet
Second Degree Brown Belt

Please log in to comment
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
}
Answered 11/30/2012 by: mac-duff
Second Degree Blue Belt

Please log in to comment
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

Answered 11/30/2012 by: mac-duff
Second Degree Blue Belt

  • You're welcome. Don't forget to mark it as answered if it helped you. Have a good weekend too.
Please log in to comment
-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

 

 
Answered 12/05/2012 by: mac-duff
Second Degree Blue Belt

Please log in to comment
Answer this question or Comment on this question for clarity