[mapguide-commits] r10213 - in sandbox/jng/installer_wix6: Libraries/MapGuide Server Libraries/MapGuide Web Extensions scripts
svn_mapguide at osgeo.org
svn_mapguide at osgeo.org
Fri Sep 11 02:04:18 PDT 2026
Author: jng
Date: 2026-09-11 02:04:17 -0700 (Fri, 11 Sep 2026)
New Revision: 10213
Added:
sandbox/jng/installer_wix6/scripts/List-MsiFiles.ps1
Modified:
sandbox/jng/installer_wix6/Libraries/MapGuide Server/HarvestFiles.wxs
sandbox/jng/installer_wix6/Libraries/MapGuide Web Extensions/HarvestFiles.wxs
Log:
Fix incorrect mapguide_en.res location being bundled to MSI. Also add helper powershell script to easily dump out installable file listings of an MSI file to allow for easy comparison against our current baseline MSI.
Modified: sandbox/jng/installer_wix6/Libraries/MapGuide Server/HarvestFiles.wxs
===================================================================
--- sandbox/jng/installer_wix6/Libraries/MapGuide Server/HarvestFiles.wxs 2026-09-10 10:39:48 UTC (rev 10212)
+++ sandbox/jng/installer_wix6/Libraries/MapGuide Server/HarvestFiles.wxs 2026-09-11 09:04:17 UTC (rev 10213)
@@ -11,15 +11,15 @@
</ComponentGroup>
<ComponentGroup Id="group_SRVREPADMINFILES">
- <Files Include="$(var.MgSource)\Server\RepositoryAdmin\**\*" Directory="SERVERROOTLOCATION" />
+ <Files Include="$(var.MgSource)\Server\RepositoryAdmin\**\*" Directory="SERVERROOTLOCATION" Subdirectory="RepositoryAdmin" />
</ComponentGroup>
<ComponentGroup Id="group_SRVRESOURCESFILES">
- <Files Include="$(var.MgSource)\Server\Resources\**\*" Directory="SERVERROOTLOCATION" />
+ <Files Include="$(var.MgSource)\Server\Resources\**\*" Directory="SERVERRESOURCESLOCATION" />
</ComponentGroup>
<ComponentGroup Id="group_SRVSCHEMAFILES">
- <Files Include="$(var.MgSource)\Server\Schema\**\*" Directory="SERVERROOTLOCATION" />
+ <Files Include="$(var.MgSource)\Server\Schema\**\*" Directory="SERVERSCHEMALOCATION" />
</ComponentGroup>
<ComponentGroup Id="group_SRVWMSFILES">
Modified: sandbox/jng/installer_wix6/Libraries/MapGuide Web Extensions/HarvestFiles.wxs
===================================================================
--- sandbox/jng/installer_wix6/Libraries/MapGuide Web Extensions/HarvestFiles.wxs 2026-09-10 10:39:48 UTC (rev 10212)
+++ sandbox/jng/installer_wix6/Libraries/MapGuide Web Extensions/HarvestFiles.wxs 2026-09-11 09:04:17 UTC (rev 10213)
@@ -60,7 +60,7 @@
</ComponentGroup>
<ComponentGroup Id="group_MAPAGENTFILES">
- <Files Include="$(var.MgSource)\Web\www\mapagent\Resources\**\*" Directory="MAPAGENTLOCATION" />
+ <Files Include="$(var.MgSource)\Web\www\mapagent\Resources\**\*" Directory="MAPAGENTLOCATION" Subdirectory="Resources" />
<Files Include="$(var.MgSource)\Web\www\mapagent\*.dll" Directory="MAPAGENTLOCATION" />
<Files Include="$(var.MgSource)\Web\www\mapagent\*.exe" Directory="MAPAGENTLOCATION" />
<Files Include="$(var.MgSource)\Web\www\mapagent\*.fcgi" Directory="MAPAGENTLOCATION" />
Added: sandbox/jng/installer_wix6/scripts/List-MsiFiles.ps1
===================================================================
--- sandbox/jng/installer_wix6/scripts/List-MsiFiles.ps1 (rev 0)
+++ sandbox/jng/installer_wix6/scripts/List-MsiFiles.ps1 2026-09-11 09:04:17 UTC (rev 10213)
@@ -0,0 +1,355 @@
+<#
+.SYNOPSIS
+ Dumps the installable file listing (target paths) from one or more MSI files,
+ optionally filtered by which features are enabled/disabled.
+
+.DESCRIPTION
+ Uses the Windows Installer COM automation object (no extra dependencies) to read the
+ Directory, Component, Feature, FeatureComponents and File tables and reconstruct the
+ full target path of every file the MSI installs. Output is one path per line, sorted,
+ which makes it easy to diff two MSIs to confirm file coverage.
+
+ Feature support:
+ -ListFeatures Print the feature tree (id / title / parent) and exit.
+ -Features "A,B" Only list files belonging to the given features. Enabling a
+ feature also enables its descendant features (mirrors MSI).
+ -ExcludeFeatures "A,B" List all files EXCEPT those belonging to the given features
+ (and their descendants). Can be combined with -Features.
+
+.EXAMPLE
+ .\List-MsiFiles.ps1 .\MapGuide.msi -Features ServerFeature
+
+.EXAMPLE
+ .\List-MsiFiles.ps1 .\MapGuide.msi -ExcludeFeatures FdoArcSdeProviderFeature
+
+.EXAMPLE
+ # full coverage comparison between two MSIs
+ .\List-MsiFiles.ps1 .\old.msi -RelativeTo INSTALLLOCATION -Quiet > old.txt
+ .\List-MsiFiles.ps1 .\new.msi -RelativeTo INSTALLLOCATION -Quiet > new.txt
+ Compare-Object (Get-Content old.txt) (Get-Content new.txt)
+
+.PARAMETER Path
+ One or more MSI files to inspect.
+
+.PARAMETER RelativeTo
+ Optional Directory Id (e.g. INSTALLLOCATION). When specified, paths are printed
+ relative to that directory instead of as full absolute paths.
+
+.PARAMETER Quiet
+ Suppress the per-file header lines so only the paths are printed.
+
+.PARAMETER ListFeatures
+ List the feature tree instead of the file listing.
+
+.PARAMETER Features
+ Comma-separated feature Ids to include (plus their descendants).
+
+.PARAMETER ExcludeFeatures
+ Comma-separated feature Ids to exclude (plus their descendants).
+#>
+[CmdletBinding()]
+param(
+ [Parameter(Mandatory = $true, ValueFromRemainingArguments = $true, Position = 0)]
+ [string[]]$Path,
+
+ [Parameter()]
+ [string]$RelativeTo,
+
+ [Parameter()]
+ [switch]$Quiet,
+
+ [Parameter()]
+ [switch]$ListFeatures,
+
+ [Parameter()]
+ [string]$Features,
+
+ [Parameter()]
+ [string]$ExcludeFeatures
+)
+
+Set-StrictMode -Version 2.0
+$ErrorActionPreference = 'Stop'
+
+# Best-effort resolution of the standard directories that MSI resolves at install time.
+# Anything not listed here is left as its raw directory id (still fine for diffing).
+$script:StandardDirs = [ordered]@{
+ 'TARGETDIR' = ''
+ 'ProgramFilesFolder' = 'C:\Program Files (x86)'
+ 'ProgramFiles64Folder' = 'C:\Program Files'
+ 'CommonFilesFolder' = 'C:\Program Files (x86)\Common Files'
+ 'CommonFiles64Folder' = 'C:\Program Files\Common Files'
+ 'WindowsFolder' = 'C:\Windows'
+ 'SystemFolder' = 'C:\Windows\System32'
+ 'System32Folder' = 'C:\Windows\System32'
+ 'ProgramMenuFolder' = 'C:\ProgramData\Microsoft\Windows\Start Menu\Programs'
+ 'StartupFolder' = 'C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup'
+ 'DesktopFolder' = 'C:\Users\Public\Desktop'
+ 'FavoritesFolder' = 'C:\Users\Public\Favorites'
+ 'FontsFolder' = 'C:\Windows\Fonts'
+ 'TempFolder' = 'C:\Windows\Temp'
+ 'TemplateFolder' = 'C:\ProgramData\Microsoft\Windows\Templates'
+}
+
+function Get-RecordField {
+ param([object]$Record, [int]$Index)
+ return $Record.GetType().InvokeMember('StringData', 'GetProperty', $null, $Record, @($Index))
+}
+
+function Invoke-MsiQuery {
+ param([object]$Db, [string]$Sql)
+ $view = $Db.GetType().InvokeMember('OpenView', 'InvokeMethod', $null, $Db, @($Sql))
+ [void]$view.GetType().InvokeMember('Execute', 'InvokeMethod', $null, $view, $null)
+ $rows = New-Object System.Collections.ArrayList
+ $rec = $view.GetType().InvokeMember('Fetch', 'InvokeMethod', $null, $view, $null)
+ while ($null -ne $rec) {
+ [void]$rows.Add($rec)
+ $rec = $view.GetType().InvokeMember('Fetch', 'InvokeMethod', $null, $view, $null)
+ }
+ [void]$view.GetType().InvokeMember('Close', 'InvokeMethod', $null, $view, $null)
+ return $rows
+}
+
+function Get-DirectoryName {
+ param([string]$DefaultDir)
+ if ([string]::IsNullOrEmpty($DefaultDir)) { return '' }
+ if ($DefaultDir -eq '.') { return '' }
+ # property-resolved directory marker (e.g. "name:." or ".:name")
+ $DefaultDir = $DefaultDir -replace ':+\.$', ''
+ $DefaultDir = $DefaultDir -replace '^\.:', ''
+ # short|long -> long
+ if ($DefaultDir.Contains('|')) {
+ return $DefaultDir.Substring($DefaultDir.IndexOf('|') + 1)
+ }
+ return $DefaultDir
+}
+
+function Open-Msi {
+ param([string]$Msi)
+ if (-not (Test-Path -LiteralPath $Msi)) { throw "MSI file not found: $Msi" }
+ $full = (Resolve-Path -LiteralPath $Msi).Path
+ $installer = New-Object -ComObject WindowsInstaller.Installer
+ return $installer.GetType().InvokeMember('OpenDatabase', 'InvokeMethod', $null, $installer, @($full, 0))
+}
+
+function Split-FeatureList {
+ param([string]$Value)
+ if ([string]::IsNullOrWhiteSpace($Value)) { return ,@() }
+ $result = @($Value -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' })
+ return ,$result
+}
+
+function Get-DescendantFeatures {
+ param([string]$Root, [hashtable]$Children)
+ $result = New-Object System.Collections.Generic.HashSet[string]
+ $queue = New-Object System.Collections.Queue
+ [void]$queue.Enqueue($Root)
+ while ($queue.Count -gt 0) {
+ $cur = $queue.Dequeue()
+ if (-not $result.Add($cur)) { continue }
+ if ($Children.ContainsKey($cur)) {
+ foreach ($c in $Children[$cur]) { [void]$queue.Enqueue($c) }
+ }
+ }
+ return $result
+}
+
+function Read-MsiData {
+ param([string]$Msi)
+ $db = Open-Msi -Msi $Msi
+
+ $dirs = @{}
+ foreach ($rec in (Invoke-MsiQuery $db 'SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`')) {
+ $id = Get-RecordField $rec 1
+ $dirs[$id] = [pscustomobject]@{
+ Parent = Get-RecordField $rec 2
+ Name = Get-DirectoryName (Get-RecordField $rec 3)
+ IsStandard = $script:StandardDirs.Contains($id)
+ }
+ }
+
+ $compDir = @{}
+ foreach ($rec in (Invoke-MsiQuery $db 'SELECT `Component`, `Directory_` FROM `Component`')) {
+ $compDir[(Get-RecordField $rec 1)] = Get-RecordField $rec 2
+ }
+
+ # Features: id -> { Parent, Title, Level }
+ $featInfo = @{}
+ $featChildren = @{}
+ foreach ($rec in (Invoke-MsiQuery $db 'SELECT `Feature`, `Feature_Parent`, `Title`, `Level` FROM `Feature`')) {
+ $id = Get-RecordField $rec 1
+ $parent = Get-RecordField $rec 2
+ $featInfo[$id] = [pscustomobject]@{
+ Parent = $parent
+ Title = Get-RecordField $rec 3
+ Level = Get-RecordField $rec 4
+ }
+ if (-not [string]::IsNullOrEmpty($parent)) {
+ if (-not $featChildren.ContainsKey($parent)) { $featChildren[$parent] = New-Object System.Collections.ArrayList }
+ [void]$featChildren[$parent].Add($id)
+ }
+ }
+
+ # FeatureComponents: feature -> components ; component -> features
+ $featComp = @{}
+ $compFeat = @{}
+ foreach ($rec in (Invoke-MsiQuery $db 'SELECT `Feature_`, `Component_` FROM `FeatureComponents`')) {
+ $f = Get-RecordField $rec 1
+ $c = Get-RecordField $rec 2
+ if (-not $featComp.ContainsKey($f)) { $featComp[$f] = New-Object System.Collections.Generic.HashSet[string] }
+ [void]$featComp[$f].Add($c)
+ if (-not $compFeat.ContainsKey($c)) { $compFeat[$c] = New-Object System.Collections.ArrayList }
+ [void]$compFeat[$c].Add($f)
+ }
+
+ # File table -> FileName, Component_, FileSize
+ $files = New-Object System.Collections.ArrayList
+ foreach ($rec in (Invoke-MsiQuery $db 'SELECT `FileName`, `Component_`, `FileSize` FROM `File`')) {
+ $fileName = Get-RecordField $rec 1
+ $comp = Get-RecordField $rec 2
+ $size = Get-RecordField $rec 3
+ if ($fileName.Contains('|')) { $fileName = $fileName.Substring($fileName.IndexOf('|') + 1) }
+ [void]$files.Add([pscustomobject]@{
+ FileName = $fileName
+ Component = $comp
+ Size = if ([string]::IsNullOrEmpty($size)) { 0 } else { [long]$size }
+ })
+ }
+
+ return [pscustomobject]@{
+ Dirs = $dirs
+ CompDir = $compDir
+ FeatInfo = $featInfo
+ FeatChild = $featChildren
+ FeatComp = $featComp
+ CompFeat = $compFeat
+ Files = $files
+ }
+}
+
+function Resolve-DirectoryPath {
+ param([string]$Id, [hashtable]$Dirs)
+ $segments = New-Object System.Collections.ArrayList
+ $cur = $Id
+ $guard = 0
+ while ($null -ne $cur -and $Dirs.ContainsKey($cur)) {
+ $node = $Dirs[$cur]
+ if ($node.IsStandard) {
+ $prefix = $script:StandardDirs[$cur]
+ if ($prefix) { [void]$segments.Insert(0, $prefix.TrimEnd('\')) }
+ break
+ }
+ if ($node.Name) { [void]$segments.Insert(0, $node.Name) }
+ if ($null -eq $node.Parent -or $node.Parent -eq '') { break }
+ $cur = $node.Parent
+ if (++$guard -gt 1000) { break }
+ }
+ return ($segments -join '\')
+}
+
+function Write-FeatureTree {
+ param($Info, $Children, [string]$Root, [int]$Depth)
+ $pad = ' ' * $Depth
+ $node = $Info[$Root]
+ $title = if ($null -ne $node -and $node.Title) { $node.Title } else { '' }
+ Write-Output ("{0}{1} ({2})" -f $pad, $Root, $title)
+ if ($Children.ContainsKey($Root)) {
+ foreach ($c in ($Children[$Root] | Sort-Object)) {
+ Write-FeatureTree -Info $Info -Children $Children -Root $c -Depth ($Depth + 1)
+ }
+ }
+}
+
+foreach ($msi in $Path) {
+ $data = Read-MsiData -Msi $msi
+
+ if ($ListFeatures) {
+ if (-not $Quiet) {
+ Write-Output ("# {0}" -f (Split-Path -Leaf $msi))
+ }
+ # top-level features (no parent)
+ $roots = $data.FeatInfo.Keys | Where-Object { [string]::IsNullOrEmpty($data.FeatInfo[$_].Parent) } | Sort-Object
+ foreach ($r in $roots) {
+ Write-FeatureTree -Info $data.FeatInfo -Children $data.FeatChild -Root $r -Depth 0
+ }
+ if (-not $Quiet) { Write-Output '' }
+ continue
+ }
+
+ # --- Resolve which features are enabled / excluded ---
+ $enabled = $null
+ $excluded = $null
+
+ $incList = Split-FeatureList $Features
+ if ($incList.Count -gt 0) {
+ $enabled = New-Object System.Collections.Generic.HashSet[string]
+ foreach ($f in $incList) {
+ if (-not $data.FeatInfo.ContainsKey($f)) {
+ Write-Warning "Feature not found in MSI: $f"
+ continue
+ }
+ foreach ($d in (Get-DescendantFeatures -Root $f -Children $data.FeatChild)) {
+ [void]$enabled.Add($d)
+ }
+ }
+ }
+
+ $excList = Split-FeatureList $ExcludeFeatures
+ if ($excList.Count -gt 0) {
+ $excluded = New-Object System.Collections.Generic.HashSet[string]
+ foreach ($f in $excList) {
+ if (-not $data.FeatInfo.ContainsKey($f)) {
+ Write-Warning "Feature not found in MSI: $f"
+ continue
+ }
+ foreach ($d in (Get-DescendantFeatures -Root $f -Children $data.FeatChild)) {
+ [void]$excluded.Add($d)
+ }
+ }
+ }
+
+ # --- Anchor for relative output ---
+ $anchor = ''
+ if ($RelativeTo) {
+ if (-not $data.Dirs.ContainsKey($RelativeTo)) { throw "Directory id not found in MSI: $RelativeTo" }
+ $anchor = Resolve-DirectoryPath -Id $RelativeTo -Dirs $data.Dirs
+ }
+ $anchorPrefix = if ($anchor) { $anchor.TrimEnd('\') + '\' } else { '' }
+
+ # --- Build the file list, applying feature filter ---
+ $targets = New-Object System.Collections.ArrayList
+ foreach ($f in $data.Files) {
+ $fileFeats = @()
+ if ($data.CompFeat.ContainsKey($f.Component)) { $fileFeats = $data.CompFeat[$f.Component] }
+
+ if ($null -ne $enabled) {
+ $any = $false
+ foreach ($ff in $fileFeats) { if ($enabled.Contains($ff)) { $any = $true; break } }
+ if (-not $any) { continue }
+ }
+ if ($null -ne $excluded) {
+ $skip = $false
+ foreach ($ff in $fileFeats) { if ($excluded.Contains($ff)) { $skip = $true; break } }
+ if ($skip) { continue }
+ }
+
+ $dirId = $data.CompDir[$f.Component]
+ $dirPath = ''
+ if ($null -ne $dirId -and $dirId -ne '') {
+ $dirPath = Resolve-DirectoryPath -Id $dirId -Dirs $data.Dirs
+ }
+ $target = if ($dirPath) { "$dirPath\$($f.FileName)" } else { $f.FileName }
+
+ if ($anchorPrefix -and $target.StartsWith($anchorPrefix, [System.StringComparison]::OrdinalIgnoreCase)) {
+ $target = $target.Substring($anchorPrefix.Length)
+ }
+ [void]$targets.Add($target)
+ }
+
+ if (-not $Quiet) {
+ Write-Output ("# {0}" -f (Split-Path -Leaf $msi))
+ Write-Output ("# total files: {0}" -f $targets.Count)
+ }
+ $targets | Sort-Object -Unique | ForEach-Object { Write-Output $_ }
+ if (-not $Quiet) { Write-Output '' }
+}
More information about the mapguide-commits
mailing list