locked
自作プログラムによる、アプリケーションのアンインストール方法について RRS feed

  • 質問

  • こんにちは

    ある特定のアプリケーションがPCにインストールされているか検索し、見つけた場合はアンインストールする
    という動作のプログラムを、VB scriptで作成することを考えています。
    ・アプリケーションはMSIでインストールされているものとします。
    ・検索方法は、アプリケーションの製品名とします。

    アンインストール方法について、下記の3つのうち、どれが一番適切か、
    お教えいただけますでしょうか。

    よろしくお願いします。

    パターン1
      1. Win32_Product class を使い、インストール済みプログラムの一覧を取得する
        (またはクエリーを使用して検索する)
      2. Win32_Product classのLocalPackageプロパティまたはPackageCacheプロパティを使用して
         C:\Windows\Installer配下にキャッシュされているMSIファイル名を取得する
      3. msiexec.exe /uninstall [取得したMSIファイル名] /norestart コマンドでアンインストールする
         
         今のところ、自作プログラムで一番うまくいくのがこのパターンです。
         ネットで調べた感じでは下記のパターン2が本来の方法のようですが、
         パターン1と2では、差が出てくる場面があるでしょうか?
     

    パターン2
      1. レジストリーから
         \HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\[GUID]
         でアプリケーションに対応するProduct Code(GUID形式)を調べて
         msiexec.exe /uninstall {GUID} でアンインストールする
        
         これが一般的な方法のようです。
         コマンドプロンプトで試しにやってみると、確かにうまくいきます。
         問題は、自作プログラムからのProductCodeの取得方法です。
         アプリケーション名をキーにレジストリ内をサーチする以外にProductCodeを取得する方法はありますでしょうか?
         Win32_Product classで、PackageCodeは取得できますが、これは別物で、今回の用途では使えないようです。
             

    パターン3
      1. パターン1と同じ
      2. Win32_Product classのUninstallメソッドでアンインストールする
     
         アンインストール後に再起動が必要なアプリケーションをUninstallメソッドでアンインストールすると、
         ユーザーに確認もせず、いきなりPCが再起動する。結局、使い物にならない。
         Uninstallメソッドはオプション指定がないため、再起動の実行の制御ができないです。
     

    2016年3月3日 10:52

回答

  • 検証してみたところパターン1、パターン2のどちらの方法も動作レベルでは問題はないと感じました。

    気になるところで言うと、パターン1については、WMIクラスなどの仕様変更があると将来動かなくなるかもしれない(たまにあるんです...)ということと、Windowsのサービスを使用しているのでそのサービスの安定性の上に成り立っているということです。

      

    WMI が動作しない  ←こちらのサイトにWMIに関するトラブルシューティングがまとめられていました。(ちょっと古いサイトですが)


    あと、インストール済アプリケーションの全リスト取得したところ完了し終わるまで私の環境で2分弱と少し時間がかかるなと感じました。※開発環境などたくさんインストールしている環境だからかもしれませんが・・・

    パターン2については、主だった懸念点はないのですが、独自でレジストリを探索する場合、64ビットOSの場合は対象が32or64ビットかを見分けてWOW6432Nodeの方も探索する必要があります。

    MSIのProductCodeの確認方法ですが、Visual Studioではプロジェクトのプロパティから確認できます。

    また、MSIファイルにも生テキストで埋まっているのでバイナリエディタなどで開き「ProductCode」という文字列の直前のGUID値がProductCodeになっているようです。

    プログラムからも取得可能です。以下はVB.NETのコードになります。参考になりましたら幸いです。

    Imports System.Text
    
    Public Class Form1
        <Runtime.InteropServices.DllImport("msi.dll")> Public Shared Function MsiOpenPackageEx(ByVal szPackagePath As String, dwOptions As Integer, ByRef hProduct As Integer) As UInteger
        End Function
        <Runtime.InteropServices.DllImport("msi.dll")> Public Shared Function MsiCloseHandle(ByVal hProduct As Integer) As UInteger
        End Function
        <Runtime.InteropServices.DllImport("msi.dll")> Public Shared Function MsiGetProductProperty(ByVal hProduct As IntPtr, ByVal szProperty As String, ByVal lpValueBuf As StringBuilder, ByRef pcchValueBuf As Integer) As UInteger
        End Function
    
        Private Const ERROR_SUCCESS As Integer = 0
        Private Const MSIOPENPACKAGEFLAGS_IGNOREMACHINESTATE As Integer = 1
        Private Const BufSize As Integer = 128
    
        Private Sub DragDrop(sender As Object, e As DragEventArgs) Handles MyBase.DragDrop
            Dim hProduct As IntPtr = IntPtr.Zero
            Dim guid As New StringBuilder(BufSize)
            Dim guidLength As Integer = guid.Capacity
            Dim fileName As String() = CType(e.Data.GetData(DataFormats.FileDrop, False), String())
            For Each filepath As String In fileName
                If MsiOpenPackageEx(filepath, MSIOPENPACKAGEFLAGS_IGNOREMACHINESTATE, hProduct) = ERROR_SUCCESS Then
                    If MsiGetProductProperty(hProduct, "ProductCode", guid, guidLength) = ERROR_SUCCESS Then
                        TextBox1.AppendText(guid.ToString() & vbCrLf)
                    End If
                    MsiCloseHandle(hProduct)
                End If
            Next
        End Sub
    
        Private Sub DragEnter(sender As Object, e As DragEventArgs) Handles MyBase.DragEnter
            e.Effect = DragDropEffects.Copy
        End Sub
    End Class






    • 編集済み kenjinoteMVP 2016年3月4日 12:40
    • 回答の候補に設定 佐伯玲 2016年3月7日 4:04
    • 回答としてマーク athirano1 2016年3月10日 11:03
    2016年3月4日 12:15

すべての返信

  • 検証してみたところパターン1、パターン2のどちらの方法も動作レベルでは問題はないと感じました。

    気になるところで言うと、パターン1については、WMIクラスなどの仕様変更があると将来動かなくなるかもしれない(たまにあるんです...)ということと、Windowsのサービスを使用しているのでそのサービスの安定性の上に成り立っているということです。

      

    WMI が動作しない  ←こちらのサイトにWMIに関するトラブルシューティングがまとめられていました。(ちょっと古いサイトですが)


    あと、インストール済アプリケーションの全リスト取得したところ完了し終わるまで私の環境で2分弱と少し時間がかかるなと感じました。※開発環境などたくさんインストールしている環境だからかもしれませんが・・・

    パターン2については、主だった懸念点はないのですが、独自でレジストリを探索する場合、64ビットOSの場合は対象が32or64ビットかを見分けてWOW6432Nodeの方も探索する必要があります。

    MSIのProductCodeの確認方法ですが、Visual Studioではプロジェクトのプロパティから確認できます。

    また、MSIファイルにも生テキストで埋まっているのでバイナリエディタなどで開き「ProductCode」という文字列の直前のGUID値がProductCodeになっているようです。

    プログラムからも取得可能です。以下はVB.NETのコードになります。参考になりましたら幸いです。

    Imports System.Text
    
    Public Class Form1
        <Runtime.InteropServices.DllImport("msi.dll")> Public Shared Function MsiOpenPackageEx(ByVal szPackagePath As String, dwOptions As Integer, ByRef hProduct As Integer) As UInteger
        End Function
        <Runtime.InteropServices.DllImport("msi.dll")> Public Shared Function MsiCloseHandle(ByVal hProduct As Integer) As UInteger
        End Function
        <Runtime.InteropServices.DllImport("msi.dll")> Public Shared Function MsiGetProductProperty(ByVal hProduct As IntPtr, ByVal szProperty As String, ByVal lpValueBuf As StringBuilder, ByRef pcchValueBuf As Integer) As UInteger
        End Function
    
        Private Const ERROR_SUCCESS As Integer = 0
        Private Const MSIOPENPACKAGEFLAGS_IGNOREMACHINESTATE As Integer = 1
        Private Const BufSize As Integer = 128
    
        Private Sub DragDrop(sender As Object, e As DragEventArgs) Handles MyBase.DragDrop
            Dim hProduct As IntPtr = IntPtr.Zero
            Dim guid As New StringBuilder(BufSize)
            Dim guidLength As Integer = guid.Capacity
            Dim fileName As String() = CType(e.Data.GetData(DataFormats.FileDrop, False), String())
            For Each filepath As String In fileName
                If MsiOpenPackageEx(filepath, MSIOPENPACKAGEFLAGS_IGNOREMACHINESTATE, hProduct) = ERROR_SUCCESS Then
                    If MsiGetProductProperty(hProduct, "ProductCode", guid, guidLength) = ERROR_SUCCESS Then
                        TextBox1.AppendText(guid.ToString() & vbCrLf)
                    End If
                    MsiCloseHandle(hProduct)
                End If
            Next
        End Sub
    
        Private Sub DragEnter(sender As Object, e As DragEventArgs) Handles MyBase.DragEnter
            e.Effect = DragDropEffects.Copy
        End Sub
    End Class






    • 編集済み kenjinoteMVP 2016年3月4日 12:40
    • 回答の候補に設定 佐伯玲 2016年3月7日 4:04
    • 回答としてマーク athirano1 2016年3月10日 11:03
    2016年3月4日 12:15
  • kenjinoteさん

    atihrano1です。
    回答ありがとうございます。

    パターン1で、
    >あと、インストール済アプリケーションの全リスト取得したところ完了し終わるまで私の環境で2分弱と少し時間がかかるなと感じました

    確かに環境(PC)によっては数分待たされることがありました。
    VBscriptでは途中経過の表示が難しいので、リストの取得の前後で、ブラウザの小さいウィンドウ(「インストール済みのアプリケーション確認中」と表示)を表示→消すという小技を入れたのですが、数分の待ち時間となると「もう我慢できない」レベルです。。

    MSIのProductCodeの取得方法についても、詳細な情報、ありがとうございました。
    参考にさせていただきます。

    2016年3月10日 11:03