PowerShell Automation for Verifying MST

%3CLINGO-SUB%20id%3D%22lingo-sub-2929960%22%20slang%3D%22en-US%22%3EPowerShell%20Automation%20for%20Verifying%20MST%3C%2FLINGO-SUB%3E%3CLINGO-BODY%20id%3D%22lingo-body-2929960%22%20slang%3D%22en-US%22%3E%3CP%3EJust%20finished%20putting%20together%20a%20script%20to%20Apply%20an%20MST%20to%20an%20MSI%20then%20read%20out%20the%20property%20table.%26nbsp%3B%20This%20is%20for%20an%20automation%20process%20to%20verify%20a%20submited%20MSI%20and%20MST%20meet%20our%20packaging%20standards.%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3ENeed%20a%20little%20sanity%20check%20as%20working%20with%20COM%20objects%20is%20not%20a%20strong%20point%20for%20me.%3C%2FP%3E%3CP%3EReally%20want%20to%20make%20sure%20I%20am%20closing%20the%20files%20correctly%20after%20applying%20the%20transform%20then%20querying%20the%20database.%26nbsp%3B%20I%20wasn't%20able%20to%20delete%20the%20temp%20files%20running%20in%20Powershell%20ISE%20until%20I%20did%20the%20ReleaseComObject.%26nbsp%3B%20I%20didn't%20have%20to%20do%20that%20when%20working%20with%20the%20straight%20MSI%20and%20just%20pull%20the%20properties%20from%20it%20so%20I%20hope%20this%20is%20not%20corrupting%20any%20files.%26nbsp%3B%26nbsp%3B%3C%2FP%3E%3CPRE%20class%3D%22lia-code-sample%20language-powershell%22%3E%3CCODE%3E%23%20Apply%20MST%20to%20an%20MSI%0A%23%20Based%20on%20Code%20from%3A%20https%3A%2F%2Fhinchley.net%2Farticles%2Fupdate-cab-file-and-msi-transform-via-command-line%2F%0A%24SourceMSI%20%3D%20%22C%3A%5CTemp%5CMSI%5CMSI-x64.msi%22%0A%24SourceMST%20%3D%20%22C%3A%5CTemp%5CMSI%5CMSI.mst%22%0A%0A%24TempMSI%20%3D%20%22%24SourceMSI.tmp%22%0A%24TempMST%20%3D%20%22%24SourceMST.tmp%22%0A%0ACopy-Item%20%24SourceMSI%20%24TempMSI%20-Force%0ACopy-Item%20%24SourceMST%20%24TempMST%20-Force%0A%0A%24WindowsInstaller%20%3D%20New-Object%20-ComObject%20WindowsInstaller.Installer%0A%23Open%20the%20database%20in%20Direct%20read%2Fwrite%20without%20Transaction%20(2)%0A%24MSIDatabase1%20%3D%20%24WindowsInstaller.GetType().InvokeMember('OpenDatabase'%20%2C%20'InvokeMethod'%20%2C%20%24Null%2C%20%24WindowsInstaller%2C%20%40(%24TempMSI%2C%202))%0A%0A%23%24MSIDatabase1.applytransform(%24TempMST%2C%200)%0A%0A%24MSIDatabase1.GetType().InvokeMember('ApplyTransform'%20%2C%20'InvokeMethod'%20%2C%20%24Null%20%2C%20%24MSIDatabase1%20%2C%20%40(%24TempMST%2C%200))%0A%0A%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%24Query%20%3D%20(%22SELECT%20Property%2CValue%20FROM%20Property%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23Opens%20a%20data%20view%20to%20the%20MSI%20based%20on%20the%20query%20created.%0A%20%20%20%20%20%20%20%20%20%20%20%20%24View%20%3D%20%24MSIDatabase1.GetType().InvokeMember('OpenView'%2C%20'InvokeMethod'%2C%20%24null%2C%20%24MSIDatabase1%2C%20(%24Query))%0A%20%20%20%20%20%20%20%20%20%20%20%20%24null%20%3D%20%24View.GetType().InvokeMember('Execute'%2C%20'InvokeMethod'%2C%20%24null%2C%20%24View%2C%20%24null)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%24hash%20%3D%20%40%7B%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Add%20File%20information%20(Note%20this%20adds%20the%20full%20File%20information%20Porperties%20so%20can%20call%20with%20%3CVAR%3E.File%20%7CSelect%20*%0A%20%20%20%20%20%20%20%20%20%20%20%20%24hash.Add('File'%2C%24TempMSI)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20WHILE%20(%24Record%20%3D%20%24View.GetType().InvokeMember('Fetch'%2C%20'InvokeMethod'%2C%20%24null%2C%20%24View%2C%20%24null))%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%24name%20%3D%20%24Record.GetType().InvokeMember('StringData'%2C%20'GetProperty'%2C%20%24null%2C%20%24Record%2C%201)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%24value%20%3D%20%24hashMSIValue%20%3D%20%24Record.GetType().InvokeMember('StringData'%2C%20'GetProperty'%2C%20%24null%2C%20%24Record%2C%202)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%24hash.Add(%24name%2C%24value)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%0A%23%20Push%20Hash%20table%20into%20a%20PSCustom%20object%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%24msiProperties%20%3D%20%5Bpscustomobject%5D%24hash%0A%0A%23%20I'm%20not%20sure%20If%20I%20have%20everything%20required%20to%20close%20out%20here%20properly%20from%20applying%20the%20transform.%0A%20%20%20%20%20%20%20%20%20%20%20%20%24null%20%3D%20%24MSIDatabase1.GetType().InvokeMember('Commit'%20%2C%20'InvokeMethod'%20%2C%20%24Null%20%2C%20%24MSIDatabase1%20%2C%20%24Null)%0A%20%20%20%20%20%20%20%20%20%20%20%20%24null%20%3D%20%24view.GetType().InvokeMember('Close'%2C%20'InvokeMethod'%2C%20%24null%2C%20%24view%2C%20%24null)%0A%20%20%20%20%20%20%20%20%0A%23%20Really%20important%20part%20to%20be%20able%20to%20release%20the%20opened%20files%20and%20delete%0A%20%20%20%20%20%20%20%20%20%20%20%20%24null%20%3D%20%5BRuntime.Interopservices.Marshal%5D%3A%3AReleaseComObject(%24view)%0A%20%20%20%20%20%20%20%20%20%20%20%20%24null%20%3D%20%5BRuntime.Interopservices.Marshal%5D%3A%3AReleaseComObject(%24MSIDatabase1)%0A%20%20%20%20%20%20%20%20%20%20%20%20%24null%20%3D%20%5BRuntime.Interopservices.Marshal%5D%3A%3AReleaseComObject(%24WindowsInstaller)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%5BGC%5D%3A%3ACollect()%3C%2FVAR%3E%3C%2FCODE%3E%3CVAR%3E%3C%2FVAR%3E%3C%2FPRE%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3C%2FLINGO-BODY%3E%3CLINGO-LABS%20id%3D%22lingo-labs-2929960%22%20slang%3D%22en-US%22%3E%3CLINGO-LABEL%3ESystem%20Center%20Configuration%20Manager%3C%2FLINGO-LABEL%3E%3CLINGO-LABEL%3EVisual%20Studio%3C%2FLINGO-LABEL%3E%3CLINGO-LABEL%3Ewindows%20installer%3C%2FLINGO-LABEL%3E%3C%2FLINGO-LABS%3E
Occasional Contributor

Just finished putting together a script to Apply an MST to an MSI then read out the property table.  This is for an automation process to verify a submited MSI and MST meet our packaging standards.

 

Need a little sanity check as working with COM objects is not a strong point for me.

Really want to make sure I am closing the files correctly after applying the transform then querying the database.  I wasn't able to delete the temp files running in Powershell ISE until I did the ReleaseComObject.  I didn't have to do that when working with the straight MSI and just pull the properties from it so I hope this is not corrupting any files.  

# Apply MST to an MSI
# Based on Code from: https://hinchley.net/articles/update-cab-file-and-msi-transform-via-command-line/
$SourceMSI = "C:\Temp\MSI\MSI-x64.msi"
$SourceMST = "C:\Temp\MSI\MSI.mst"

$TempMSI = "$SourceMSI.tmp"
$TempMST = "$SourceMST.tmp"

Copy-Item $SourceMSI $TempMSI -Force
Copy-Item $SourceMST $TempMST -Force

$WindowsInstaller = New-Object -ComObject WindowsInstaller.Installer
#Open the database in Direct read/write without Transaction (2)
$MSIDatabase1 = $WindowsInstaller.GetType().InvokeMember('OpenDatabase' , 'InvokeMethod' , $Null, $WindowsInstaller, @($TempMSI, 2))

#$MSIDatabase1.applytransform($TempMST, 0)

$MSIDatabase1.GetType().InvokeMember('ApplyTransform' , 'InvokeMethod' , $Null , $MSIDatabase1 , @($TempMST, 0))



            $Query = ("SELECT Property,Value FROM Property")

            #Opens a data view to the MSI based on the query created.
            $View = $MSIDatabase1.GetType().InvokeMember('OpenView', 'InvokeMethod', $null, $MSIDatabase1, ($Query))
            $null = $View.GetType().InvokeMember('Execute', 'InvokeMethod', $null, $View, $null)

                        $hash = @{}
            # Add File information (Note this adds the full File information Porperties so can call with <Var>.File |Select *
            $hash.Add('File',$TempMSI)

                WHILE ($Record = $View.GetType().InvokeMember('Fetch', 'InvokeMethod', $null, $View, $null)) 
                {
	            $name = $Record.GetType().InvokeMember('StringData', 'GetProperty', $null, $Record, 1)
	            $value = $hashMSIValue = $Record.GetType().InvokeMember('StringData', 'GetProperty', $null, $Record, 2)
	            $hash.Add($name,$value)
                }

# Push Hash table into a PSCustom object
                $msiProperties = [pscustomobject]$hash

# I'm not sure If I have everything required to close out here properly from applying the transform.
            $null = $MSIDatabase1.GetType().InvokeMember('Commit' , 'InvokeMethod' , $Null , $MSIDatabase1 , $Null)
            $null = $view.GetType().InvokeMember('Close', 'InvokeMethod', $null, $view, $null)
        
# Really important part to be able to release the opened files and delete
            $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($view)
            $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($MSIDatabase1)
            $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($WindowsInstaller)

            [GC]::Collect()

 

0 Replies