-
-
Save jstangroome/913062 to your computer and use it in GitHub Desktop.
function Get-MsiProductVersion { | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory=$true)] | |
[ValidateScript({$_ | Test-Path -PathType Leaf})] | |
[string] | |
$Path | |
) | |
function Get-Property ($Object, $PropertyName, [object[]]$ArgumentList) { | |
return $Object.GetType().InvokeMember($PropertyName, 'Public, Instance, GetProperty', $null, $Object, $ArgumentList) | |
} | |
function Invoke-Method ($Object, $MethodName, $ArgumentList) { | |
return $Object.GetType().InvokeMember($MethodName, 'Public, Instance, InvokeMethod', $null, $Object, $ArgumentList) | |
} | |
$ErrorActionPreference = 'Stop' | |
Set-StrictMode -Version Latest | |
#http://msdn.microsoft.com/en-us/library/aa369432(v=vs.85).aspx | |
$msiOpenDatabaseModeReadOnly = 0 | |
$Installer = New-Object -ComObject WindowsInstaller.Installer | |
$Database = Invoke-Method $Installer OpenDatabase @($Path, $msiOpenDatabaseModeReadOnly) | |
$View = Invoke-Method $Database OpenView @("SELECT Value FROM Property WHERE Property='ProductVersion'") | |
Invoke-Method $View Execute | |
$Record = Invoke-Method $View Fetch | |
if ($Record) { | |
Write-Output (Get-Property $Record StringData 1) | |
} | |
Invoke-Method $View Close @() | |
Remove-Variable -Name Record, View, Database, Installer | |
} |
Is it also possible to set a property?
Great!
Line 30 and Line 37 $need to have $null = Invoke-Method ...
as what they return is going back into the output which creates a 3 string array with only the second item having the version string. Making those changes results in a simple string which is what we are looking for.
Excelente aporte
Great function.
One problem I have (and haven't been able to fire a fix) it the script leaves the file locked for the remainder of the session. So if I'm checking the installer version, and getting a newer one if required, it fails.
One problem I have (and haven't been able to fire a fix) it the script leaves the file locked for the remainder of the session. So if I'm checking the installer version, and getting a newer one if required, it fails.
I haven't used this script in some time but I would try adding [System.Runtime.Interopservices.Marshal]::ReleaseComObject($Database)
between lines 37 and 38.
MicrosoftDocs/PowerShell-Docs#4617
One problem I have (and haven't been able to fire a fix) it the script leaves the file locked for the remainder of the session. So if I'm checking the installer version, and getting a newer one if required, it fails.
I haven't used this script in some time but I would try adding
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Database)
between lines 37 and 38.
MicrosoftDocs/PowerShell-Docs#4617
Yes, I found some more ideas on that as well, and garbage collection. Still no luck. It is driving me nuts!
I was also trying
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Installer) | Out-Null
[System.GC]::Collect()
I even tried doing it in a Process{} End {} construct.
I don't understand why microsoft made it so ridiculous to check a file version in such a bizarre way.
I might need to try figuring how to spawn it in a thread
I might need to try figuring how to spawn it in a thread
Not a thread, perform the check(s) in a child powershell.exe process, that will separate the scope of the objects and their corresponding file locks. If I remember correctly powershell.exe -EncodedCommand ...
is a very convenient way to run large chunks of script in another shell, maybe also ... -OutputFormat XML | Import-Clixml
to pass back rich objects.
OK I got a working version. Dealing with returns is also a pain. I moved to a reference variable...
Also, it returns 0.0.0.0 if it doesn't exist so I can assume need to retrieve it...
`function Get-MsiProductVersion {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string]
$Path,
[ref]
$Result
)
if (!(Test-Path $Path)) {
Write-Output "0.0.0.0"
} else {
#http://msdn.microsoft.com/en-us/library/aa369432(v=vs.85).aspx
$job = start-job -scriptblock {
param($Path)
function Get-Property ($Object, $PropertyName, [object[]]$ArgumentList) {
return $Object.GetType().InvokeMember($PropertyName, 'Public, Instance, GetProperty', $null, $Object, $ArgumentList)
}
function Invoke-Method ($Object, $MethodName, $ArgumentList) {
return $Object.GetType().InvokeMember($MethodName, 'Public, Instance, InvokeMethod', $null, $Object, $ArgumentList)
}
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
$msiOpenDatabaseModeReadOnly = 0
$Installer = New-Object -ComObject WindowsInstaller.Installer
$Database = Invoke-Method $Installer OpenDatabase @($Path, $msiOpenDatabaseModeReadOnly) -ErrorAction SilentlyContinue
if ($null -ne $Database) {
$View = Invoke-Method $Database OpenView @("SELECT Value FROM Property WHERE Property='ProductVersion'")
Invoke-Method $View Execute
$Record = Invoke-Method $View Fetch
if ($Record) {
Write-Output (Get-Property $Record StringData 1)
}
Invoke-Method $View Close @()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Database) | Out-Null
}
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Installer) | Out-Null
Remove-Variable -Name Record, View, Database, Installer
[System.GC]::Collect()
} -argumentlist @($Path)
$job | wait-job
$Output = Receive-Job $job -ErrorAction Stop
$Result.Value = $Output[1]
Remove-job $job
Write-Output $Result.Value
}
}
$CurrentVersion = $null
Get-MsiProductVersion -Path $InstallerPath -Result ([ref]$CurrentVersion)
Write-Output "Current Version: $CurrentVersion"
`
@Mobe1969 - Thank you for sharing this. I have been stuck with the file-lock issue when retrieving MSI data. Your bit of code did the trick!
No worries @DoublNAT
Thanks a lot of for this script