PowerShellでmsiを編集


# Visual Studioで作成したmsiは編集が必要

Visual Studio のセットアッププロジェクトで作成したmsiは編集が必要です。
（セットアッププロジェクトはVisual Studio 2012からサポートされなくなりました）

exeをバージョンアップするときは、既存のexeに上書きするのが普通です。
msiの編集は、上書きを可能にするために行います。

[msiの編集について](https://dobon.net/vb/dotnet/deployment/upgrades.html#section6)


# PowerShellでmsiを編集 

```powershell: orca.ps1
# MSIファイルのパス
$msiPath = Join-Path $PSScriptRoot "[ここにmsiのパスを描く]"
try {
    # Windows Installerオブジェクトを作成
    $installer = New-Object -ComObject WindowsInstaller.Installer
    
    # データベースを開く
    $database = $installer.OpenDatabase($msiPath, 1)
    
    try {
        # CustomActionテーブルに追加
        $record = $installer.CreateRecord(4)
        $record.StringData(1) = "fix_reinstall".Trim()
        $record.IntegerData(2) = 51
        $record.StringData(3) = "REINSTALL".Trim()
        $record.StringData(4) = "{}".Trim()
        
        $view = $database.OpenView("INSERT INTO CustomAction (Action, Type, Source, Target) VALUES (?, ?, ?, ?)")
        $view.Execute($record)
        
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($view) | Out-Null
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($record) | Out-Null

        # InstallExecuteSequenceテーブルに追加
        $record = $installer.CreateRecord(3)
        $record.StringData(1) = "fix_reinstall".Trim()
        $record.StringData(2) = "NOT Installed".Trim()
        $record.IntegerData(3) = 999
        
        $view = $database.OpenView("INSERT INTO InstallExecuteSequence (Action, Condition, Sequence) VALUES (?, ?, ?)")
        $view.Execute($record)
        
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($view) | Out-Null
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($record) | Out-Null
        
        # InstallUISequenceテーブルに追加
        $record = $installer.CreateRecord(3)
        $record.StringData(1) = "fix_reinstall".Trim()
        $record.StringData(2) = "NOT Installed".Trim()
        $record.IntegerData(3) = 999
        
        $view = $database.OpenView("INSERT INTO InstallUISequence (Action, Condition, Sequence) VALUES (?, ?, ?)")
        $view.Execute($record)
        
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($view) | Out-Null
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($record) | Out-Null
        
        $database.Commit()
        
        # 確認クエリ
        Write-Host "`n[結果]"
        
        Write-Host "`nCustomAction:"
        $view = $database.OpenView("SELECT * FROM CustomAction WHERE Action = 'fix_reinstall'")
        $view.Execute()
        $record = $view.Fetch()
        while ($record -ne $null) {
            Write-Host "  Action: $($record.StringData(1))"
            Write-Host "  Type: $($record.IntegerData(2))"
            Write-Host "  Source: $($record.StringData(3))"
            Write-Host "  Target: $($record.StringData(4))"
            $record = $view.Fetch()
        }
        
        Write-Host "`nInstallExecuteSequence:"
        $view = $database.OpenView("SELECT * FROM InstallExecuteSequence WHERE Action = 'fix_reinstall'")
        $view.Execute()
        $record = $view.Fetch()
        while ($record -ne $null) {
            Write-Host "  Action: $($record.StringData(1))"
            Write-Host "  Condition: $($record.StringData(2))"
            Write-Host "  Sequence: $($record.IntegerData(3))"
            $record = $view.Fetch()
        }
        
        Write-Host "`nInstallUISequence:"
        $view = $database.OpenView("SELECT * FROM InstallUISequence WHERE Action = 'fix_reinstall'")
        $view.Execute()
        $record = $view.Fetch()
        while ($record -ne $null) {
            Write-Host "  Action: $($record.StringData(1))"
            Write-Host "  Condition: $($record.StringData(2))"
            Write-Host "  Sequence: $($record.IntegerData(3))"
            $record = $view.Fetch()
        }
    }
    finally {
        Write-Host "`n"
        if ($record -ne $null) {
            [System.Runtime.Interopservices.Marshal]::ReleaseComObject($record) | Out-Null
        }
        if ($view -ne $null) {
            [System.Runtime.Interopservices.Marshal]::ReleaseComObject($view) | Out-Null
        }
        if ($database -ne $null) {
            [System.Runtime.Interopservices.Marshal]::ReleaseComObject($database) | Out-Null
        }
        if ($installer -ne $null) {
            [System.Runtime.Interopservices.Marshal]::ReleaseComObject($installer) | Out-Null
        }
    }
}
catch {
    Write-Host "エラーが発生しました: $_"
}
finally {
    [System.GC]::Collect()
    [System.GC]::WaitForPendingFinalizers()
}
```

# バッチからPowerShellのスクリプトを呼び出す

バッチからPowerShellのスクリプトを呼び出すときは、`powershell [ps1ファイル名]` では動かないのでオプションを追加します。

```bat: msi.bat

powershell -NoProfile -ExecutionPolicy Bypass .\orca.ps1

```

# 顛末

Visual Studioで作成したmsiは、通常のmsiとは違うようです。

orcaを使って手動で編集する以外に、セットアッププロジェクトのプロパティ`PostBuildEvent`や、VBscriptを使用することで自動化することができるはずですが、うまくいきませんでした。

手動で編集して間違えるとインストーラが動きません。
自動化するのが一番です

## 関連リンク

[なつかしの Visual Studio Installer（の後継）を現役のVisual Studioで使用する](https://qiita.com/otagaisama-1/items/39bf6852f0e99c761786)