Feature request! Decode Directory table targets...

Topics: Features
Dec 16, 2015 at 6:57 PM
Edited Dec 18, 2015 at 2:51 PM
Hi,

I created a few functions in Powershell using DTF directly until I realised that your module now supports querying MSI files! However I am still sticking with my custom script as I need to be able to decode the Directory table entries.

This was a bit tricky as I had to hook in to run some any SetProperty or SetDirectory custom actions (since some MSIs run CAs to set INSTALLDIR for example), then some standard costing actions in order for the Session.GetTargetPath function to work. The example function below takes the path to an MSI and returns a hash table of directory table keys paired up with the full real path. It also adds on any missing directories (e.g. I had an MSI with [SystemFolder] referenced in the MSI but it was not in the directory table).
Function Get-MsiDirectories 
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $True,
                ValueFromPipeline = $True,
        HelpMessage = 'Path to MSI package')]
        [string]$MsiPath
    )
    Write-Verbose -Message "Analysing Directory table for $MsiPath"
    $Database = New-Object -TypeName WixToolset.Dtf.WindowsInstaller.Database -ArgumentList ($MsiPath)
    $null = [WixToolset.Dtf.WindowsInstaller.Installer]::SetInternalUI('Silent')
    $Session = [WixToolset.Dtf.WindowsInstaller.Installer]::OpenPackage($Database,$True)

    $SqlQuery = 'SELECT Action, Condition, Sequence FROM InstallExecuteSequence ORDER BY Sequence'

    $View = $Database.OpenView($SqlQuery)
    $View.Execute()
    $Record = $View.Fetch()
        
    Write-Verbose -Message 'Evaluating Custom Actions...'

    While ($Record -ne $null) 
    {
        $CustomAction = $Record.GetString(1)
        $CustomActionType = $null

        Try 
        {
            $SqlQuery2 = "SELECT Type FROM CustomAction WHERE Action = '$CustomAction'"
            $View2 = $Database.OpenView($SqlQuery2)
            $View2.Execute()
            $Record2 = $View2.Fetch()
                        
            If ($Record2 -ne $null) 
            {
                $CustomActionType = $Record2.GetString(1)
                If (($CustomActionType -band 51) -eq 51) 
                {
                    $CustomActionType = 'SetProperty'
                }
                ElseIf (($CustomActionType -band 35) -eq 35) 
                {
                    $CustomActionType = 'SetDirectory'
                }
            }

            $View2.Close()
        }
        Catch 
        {
            Write-Verbose -Message 'Error reading from CustomAction table'
        }
        Finally
        {

        }

        If ($CustomActionType -eq 'SetProperty' -or $CustomActionType -eq 'SetDirectory' -or $CustomAction -eq 'CostInitialize' -or $CustomAction -eq 'FileCost' -or $CustomAction -eq 'CostFinalize') 
        {
            $Condition = $Record.GetString(2)
            $ConditionResult = $Session.EvaluateCondition($Condition,$True)

            Write-Verbose -Message "Custom Action found: $CustomAction"
            If ($CustomActionType) 
            {
                Write-Verbose -Message "  Type: $CustomActionType"
            }
            If ($Condition) 
            {
                Write-Verbose -Message "  Condition: $Condition = $ConditionResult"
            }
                
            If ($ConditionResult) 
            {
                Write-Verbose -Message "  Doing action $CustomAction"
                $Session.DoAction($CustomAction)
            }
            Else 
            {
                Write-Verbose -Message "  Skipping action $CustomAction"
            }               
        }
        $Record = $View.Fetch()
    }

    $SqlQuery = 'SELECT Directory FROM Directory'
    $View = $Database.OpenView($SqlQuery)
    $View.Execute()
    $Record = $View.Fetch()
    $Directories = New-Object System.Collections.Specialized.OrderedDictionary
    While ($Record -ne $null) 
    {
        $Directory = $Record.GetString(1)
        $DecodedDirectory = $Session.GetTargetPath($Record.GetString(1))
        Write-Verbose -Message "$Directory = $DecodedDirectory"
        $Directories.Add($Directory, $DecodedDirectory)
        $Record = $View.Fetch()
    }
     
    $Session.Dispose()
    $View.Close()
    $Database.Dispose()

    If (!$Directories.Contains('AdminToolsFolder'))
    {
        $Directories.Add('AdminToolsFolder','C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Administrative Tools\')
    }
    If (!$Directories.Contains('AppDataFolder')) 
    {
        $Directories.Add('AppDataFolder','C:\Users\<UserName>\AppData\Roaming\')
    }
    If (!$Directories.Contains('CommonAppDataFolder')) 
    {
        $Directories.Add('CommonAppDataFolder','C:\ProgramData\')
    }
    If (!$Directories.Contains('CommonFilesFolder')) 
    {
        $Directories.Add('CommonFilesFolder','C:\Program Files (x86)\Common Files\')
    }
    If (!$Directories.Contains('CommonFiles64Folder')) 
    {
        $Directories.Add('CommonFiles64Folder','C:\Program Files (x86)\Common Files\')
    }
    If (!$Directories.Contains('DesktopFolder')) 
    {
        $Directories.Add('DesktopFolder','C:\Users\Public\Desktop\')
    }
    If (!$Directories.Contains('FavoritesFolder')) 
    {
        $Directories.Add('FavoritesFolder','C:\Users\<UserName>\Favorites\')
    }
    If (!$Directories.Contains('FontsFolder')) 
    {
        $Directories.Add('FontsFolder','C:\Windows\Fonts\')
    }
    If (!$Directories.Contains('LocalAppDataFolder')) 
    {
        $Directories.Add('LocalAppDataFolder','C:\Users\<UserName>\AppData\Local\')
    }
    If (!$Directories.Contains('MyPicturesFolder')) 
    {
        $Directories.Add('MyPicturesFolder','C:\Users\<UserName>\Pictures\')
    }
    If (!$Directories.Contains('NetHoodFolder')) 
    {
        $Directories.Add('NetHoodFolder','C:\Users\<UserName>\AppData\Roaming\Microsoft\Windows\Network Shortcuts\')
    }
    If (!$Directories.Contains('PersonalFolder')) 
    {
        $Directories.Add('PersonalFolder','C:\Users\<UserName>\Documents\')
    }
    If (!$Directories.Contains('PrintHoodFolder')) 
    {
        $Directories.Add('PrintHoodFolder','C:\Users\<UserName>\AppData\Roaming\Microsoft\Windows\Printer Shortcuts\')
    }
    If (!$Directories.Contains('ProgramFiles64Folder')) 
    {
        $Directories.Add('ProgramFiles64Folder','C:\Program Files\')
    }
    If (!$Directories.Contains('ProgramFilesFolder')) 
    {
        $Directories.Add('ProgramFilesFolder','C:\Program Files (x86)\')
    }
    If (!$Directories.Contains('ProgramMenuFolder')) 
    {
        $Directories.Add('ProgramMenuFolder','C:\ProgramData\Microsoft\Windows\Start Menu\Programs\')
    }
    If (!$Directories.Contains('RecentFolder')) 
    {
        $Directories.Add('RecentFolder','C:\Users\<UserName>\AppData\Roaming\Microsoft\Windows\Recent\')
    }
    If (!$Directories.Contains('SendToFolder')) 
    {
        $Directories.Add('SendToFolder','C:\Users\<UserName>\AppData\Roaming\Microsoft\Windows\SendTo\')
    }
    If (!$Directories.Contains('StartMenuFolder')) 
    {
        $Directories.Add('StartMenuFolder','C:\ProgramData\Microsoft\Windows\Start Menu\')
    }
    If (!$Directories.Contains('StartupFolder')) 
    {
        $Directories.Add('StartupFolder','C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\')
    }
    If (!$Directories.Contains('System16Folder')) 
    {
        $Directories.Add('System16Folder','C:\Windows\System\')
    }
    If (!$Directories.Contains('System64Folder')) 
    {
        $Directories.Add('System64Folder','C:\Windows\System32\')
    }
    If (!$Directories.Contains('SystemFolder')) 
    {
        $Directories.Add('SystemFolder','C:\Windows\SysWOW64\')
    }
    If (!$Directories.Contains('TARGETDIR')) 
    {
        $Directories.Add('TARGETDIR','C:\')
    }
    If (!$Directories.Contains('TempFolder')) 
    {
        $Directories.Add('TempFolder','C:\Users\<UserName>\AppData\Local\Temp\')
    }
    If (!$Directories.Contains('TemplateFolder')) 
    {
        $Directories.Add('TemplateFolder','C:\ProgramData\Microsoft\Windows\Templates\')
    }
    If (!$Directories.Contains('WindowsFolder')) 
    {
        $Directories.Add('WindowsFolder','C:\Windows\')
    }
    If (!$Directories.Contains('WindowsVolume')) 
    {
        $Directories.Add('WindowsVolume','C:\')
    }
    
    Write-Verbose -Message 'Finished analysing Directory table.'
    Write-Output -InputObject $Directories
}
Note this needs Wix 4.0 installed (or at least access to that dll referenced).