0

I have the below function:

Function Get-MsiProperty
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true)]
        [ValidateScript({$_ | Test-Path -PathType Leaf})]
        [string]$Path,

        [Parameter(Mandatory=$true)]
        [string]$Property
    )

    Begin
    {       
        $GetProperty = {
            Param
            (
                $Object,
                $PropertyName,
                [object[]]$ArgumentList
            )
            Return $Object.GetType().InvokeMember($PropertyName, 'Public, Instance, GetProperty', $null, $Object, $ArgumentList)
        }

        $InvokeMethod = {
            Param
            (
                $Object,
                $MethodName,
                $ArgumentList
            )
            Return $Object.GetType().InvokeMember($MethodName, 'Public, Instance, InvokeMethod', $null, $Object, $ArgumentList)
        }

        ${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
        $PSParameters = $PSBoundParameters | Format-Table -Auto | Out-String
    }
    Process
    {
        Try
        {
            Set-StrictMode -Version Latest

            # http://msdn.microsoft.com/en-us/library/aa369432(v=vs.85).aspx
            $msiOpenDatabaseModeReadOnly = 0
            $Installer = New-Object -ComObject WindowsInstaller.Installer -ErrorAction 'Stop'

            $Database = &$InvokeMethod -Object $Installer -MethodName OpenDatabase -ArgumentList @($Path, $msiOpenDatabaseModeReadOnly)

            $View = &$InvokeMethod -Object $Database -MethodName OpenView -ArgumentList @("SELECT Value FROM Property WHERE Property='$Property'")

            &$InvokeMethod -Object $View -MethodName Execute | Out-Null

            $MSIProperty = $null
            $Record = &$InvokeMethod -Object $View -MethodName Fetch
            If ($Record)
            {
                $MSIProperty = &$GetProperty -Object $Record -PropertyName StringData -ArgumentList 1
            }
            Write-Output $MSIProperty
        }
        Catch
        {
            Write-Host -Message "Failed to get the MSI property [$Property]"
        }
        Finally
        {
             &$InvokeMethod -Object $View -MethodName Close -ArgumentList @() | Out-Null
        }
    }
}

If I call the function as below, I get the correct result:

$ProductCode = Get-MsiProperty -Path "ConfigMgrTools.msi" -Property 'ProductCode'

If I call the function as below, the result has a space before and after the result. Why does this happen? I have used Get-Member to analyze the variable it shows up as a 'system.string' both times.

[string]$ProductCode = Get-MsiProperty -Path "ConfigMgrTools.msi" -Property 'ProductCode'
3
  • 1
    Maybe Execute and Close return $null? Try assigning the result of those calls to $null, e.g. $null = &$InvokeMethod -Object $View -MethodName Close -ArgumentList @() Commented Aug 29, 2014 at 2:18
  • That was exactly it. You sir are a genius. I've dealt with this type of issue when executing C# code in PowerShell but it just never crossed my mind that might be the case here. If you post it as an answer, I'll mark it answered. Commented Aug 29, 2014 at 3:20
  • Original post has been edited to send the problematic line of code to Out-Null as Jason Shirk suggested above. Commented Aug 29, 2014 at 11:58

1 Answer 1

1

The original unedited code was unintentionally writing out $null in two places.

When $ProductCode is untyped, the result is an array, and when formatting the array, the $null values in the array are ignored.

When $ProductCode is typed as a string, the $null values are formatted with a space, e.g.

PS> "__{0}__" -f ([string]@($null, "abc", $null))
__ abc __

Compare that with untyped (using Out-String -Stream to convert the array to a string instead of a type cast):

PS> "_{0}_" -f (@($null, "abc", $null) | out-string -Stream)
_abc_

The $null values typically come from .Net method calls, or in this case, COM method calls. I usually assign the result of method calls like this to $null because that has the best performance in all versions of PowerShell, but piping to Out-Null or casting to [void] both work equivalently so you can choose whichever you prefer.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.