none
ExcelとPowerShellでCSVファイルの保存 RRS feed

  • 質問

  • PowerShellのサンプルを使って、作成したExcelブック(ワークシート)をCSV形式でセーブしたいのですが

    $a = New-Object -comobject Excel.Application 
    $a.Visible = $True 
    $b = $a.Workbooks.Add() 
    $c = $b.Worksheets.Item(1) 
    $c.Cells.Item(1,1) = "A value in cell A1." 
    $b.SaveAs("C:\Users\Test\Documents\Test.csv", $xlCSV ) 
    $a.Quit()

    のように記述しても、Excelは立ち上がるのですが、ブック形式でセーブしようとして問い合わせのダイヤログボックスが表示されてしまいます。

    またシェルの画面には

    "2" 個の引数を指定して "SaveAs" を呼び出し中に例外が発生しました: "Workbook クラスの SaveAs メソッドが失敗しました。"
    発生場所 C:\Users\Test\Documents\test.ps1:10 文字:10
    + $b.SaveAs <<<< ("C:\Users\Test\Documents\Test.xls", $xlCSV ) 
        + CategoryInfo          : NotSpecified: (:) []、MethodInvocationException
        + FullyQualifiedErrorId : ComMethodTargetInvocation

    の様なメッセージが出ます。

    質問が3つあります。

    (1)本件の場合、どの様に記述するのが正解でしょうか?

    (2)VC++でComオブジェクトを操作する際に必要になる各種定数(この場合Excel::xlCSV、その他Excel::xlWorkbookNormal など)をPowerShell上
    で使う場合、どの様に記述したらよいのでしょうか(本件の場合、$xlCSV で良い)?

    (3)Comオブジェクトの関数(Open()やSaveAs()など)の引数の並びはVC++で使用する際の引数並びと同じと考えてよいですか?

    またこのような、PowerShellをつかったMSOfficeのオートメーションに関する解説本/HPがあれば紹介をお願いします。

     

    2011年6月23日 13:54

回答

  • > (1)本件の場合、どの様に記述するのが正解でしょうか?

    保存問い合わせのダイアログは、Excel.ApplicationオブジェクトのDisplayAlertsプロパティにFalseを設定することで表示を抑制できます。

    COMオブジェクトの定数を直接参照する方法はおそらくないので、変数に対応する定数値を格納したものを用意しておく必要があると思います。
    具体的な値についてはVBAのヘルプに載ってるかと思います。もしくはVBAのオブジェクトブラウザでも調べられます。
    (WSH+VBScriptでConstを使って定義するのと同様です。WSHなら.wsfフォーマットを使えばreference要素で定数値を読み込めるのですが…)

    あと、PowerShell(というか.NET)からCOMオブジェクトを呼び出す場合は、最後にCOMオブジェクトを格納した変数すべてにReleaseComObjectメソッドを使って開放してやる必要があります。
    これをしないとExcelのプロセスが残ったままになることがあります。
    (PowerShellのプロセスが終了すればExcelのプロセスも終了するので、ps1として実行し即終了する場合ならこの手順はなくても大丈夫だと思います)

    以上をまとめるとお題のスクリプトはこんな感じになるでしょうか。

    $excel = New-Object -comobject Excel.Application  
    $xlCSV = 6
    $excel.DisplayAlerts=$false
    $excel.Visible = $True  
    $book = $excel.Workbooks.Add()  
    $sheet = $book.Worksheets.Item(1)  
    $sheet.Cells.Item(1,1) = "A value in cell A1."  
    $book.SaveAs("C:\Users\daisuke\Test.csv", $xlCSV)  
    $excel.Quit()
    $sheet,$book,$excel|
    %{[void][Runtime.InteropServices.Marshal]::ReleaseComObject($_)}

    > (2)VC++でComオブジェクトを操作する際に必要になる各種定数(この場合Excel::xlCSV、その他Excel::xlWorkbookNormal など)をPowerShell上
    > で使う場合、どの様に記述したらよいのでしょうか(本件の場合、$xlCSV で良い)?

    1で回答した通りです。

    > (3)Comオブジェクトの関数(Open()やSaveAs()など)の引数の並びはVC++で使用する際の引数並びと同じと考えてよいですか?

    PowerShellではメソッドの名前付き引数指定がサポートされていないので、COMオブジェクトの定義通りの順で引数を指定する必要があります。
    しかし省略可能引数を省略することは可能です。
    つまり基本的にはC#やVC++などと同じ並びで引数を指定しますが、省略可能引数はvtMissingやType.Missingのような「省略可能引数の既定値」を示す値を指定する必要はありません。

    よって、たとえば(1)の回答で示した通り、SaveAsメソッドは省略可能引数のうち必要なものだけ指定して実行することができるわけです。

    この辺りはVBScriptとほぼ同じです。

    なお、 ExcelのCOMオブジェクトをそのまま呼ぶのではなく、C#などで使うためにあらかじめCOMオブジェクトをラップした.NETオブジェクトを使うのも良いかと思います。

    Add-Type -AssemblyName "Microsoft.Office.Interop.Excel"
    $excel = New-Object "Microsoft.Office.Interop.Excel.ApplicationClass"
    $excel.DisplayAlerts=$false
    $excel.Visible = $True  
    $book = $excel.Workbooks.Add()  
    $sheet = $book.Worksheets.Item(1)  
    $sheet.Cells.Item(1,1) = "A value in cell A1."  
    $book.SaveAs("C:\Users\daisuke\Test.csv",  [Microsoft.Office.Interop.Excel.XlFileFormat]::xlCSV)  
    $excel.Quit()
    $sheet,$book,$excel|
    %{[void][Runtime.InteropServices.Marshal]::ReleaseComObject($_)}

    ご覧の通りこちらの方法だと定数値をコード中で参照することもできます。






    • 回答としてマーク Ruru 2011年6月23日 20:53
    2011年6月23日 15:14
    モデレータ

すべての返信

  • > (1)本件の場合、どの様に記述するのが正解でしょうか?

    保存問い合わせのダイアログは、Excel.ApplicationオブジェクトのDisplayAlertsプロパティにFalseを設定することで表示を抑制できます。

    COMオブジェクトの定数を直接参照する方法はおそらくないので、変数に対応する定数値を格納したものを用意しておく必要があると思います。
    具体的な値についてはVBAのヘルプに載ってるかと思います。もしくはVBAのオブジェクトブラウザでも調べられます。
    (WSH+VBScriptでConstを使って定義するのと同様です。WSHなら.wsfフォーマットを使えばreference要素で定数値を読み込めるのですが…)

    あと、PowerShell(というか.NET)からCOMオブジェクトを呼び出す場合は、最後にCOMオブジェクトを格納した変数すべてにReleaseComObjectメソッドを使って開放してやる必要があります。
    これをしないとExcelのプロセスが残ったままになることがあります。
    (PowerShellのプロセスが終了すればExcelのプロセスも終了するので、ps1として実行し即終了する場合ならこの手順はなくても大丈夫だと思います)

    以上をまとめるとお題のスクリプトはこんな感じになるでしょうか。

    $excel = New-Object -comobject Excel.Application  
    $xlCSV = 6
    $excel.DisplayAlerts=$false
    $excel.Visible = $True  
    $book = $excel.Workbooks.Add()  
    $sheet = $book.Worksheets.Item(1)  
    $sheet.Cells.Item(1,1) = "A value in cell A1."  
    $book.SaveAs("C:\Users\daisuke\Test.csv", $xlCSV)  
    $excel.Quit()
    $sheet,$book,$excel|
    %{[void][Runtime.InteropServices.Marshal]::ReleaseComObject($_)}

    > (2)VC++でComオブジェクトを操作する際に必要になる各種定数(この場合Excel::xlCSV、その他Excel::xlWorkbookNormal など)をPowerShell上
    > で使う場合、どの様に記述したらよいのでしょうか(本件の場合、$xlCSV で良い)?

    1で回答した通りです。

    > (3)Comオブジェクトの関数(Open()やSaveAs()など)の引数の並びはVC++で使用する際の引数並びと同じと考えてよいですか?

    PowerShellではメソッドの名前付き引数指定がサポートされていないので、COMオブジェクトの定義通りの順で引数を指定する必要があります。
    しかし省略可能引数を省略することは可能です。
    つまり基本的にはC#やVC++などと同じ並びで引数を指定しますが、省略可能引数はvtMissingやType.Missingのような「省略可能引数の既定値」を示す値を指定する必要はありません。

    よって、たとえば(1)の回答で示した通り、SaveAsメソッドは省略可能引数のうち必要なものだけ指定して実行することができるわけです。

    この辺りはVBScriptとほぼ同じです。

    なお、 ExcelのCOMオブジェクトをそのまま呼ぶのではなく、C#などで使うためにあらかじめCOMオブジェクトをラップした.NETオブジェクトを使うのも良いかと思います。

    Add-Type -AssemblyName "Microsoft.Office.Interop.Excel"
    $excel = New-Object "Microsoft.Office.Interop.Excel.ApplicationClass"
    $excel.DisplayAlerts=$false
    $excel.Visible = $True  
    $book = $excel.Workbooks.Add()  
    $sheet = $book.Worksheets.Item(1)  
    $sheet.Cells.Item(1,1) = "A value in cell A1."  
    $book.SaveAs("C:\Users\daisuke\Test.csv",  [Microsoft.Office.Interop.Excel.XlFileFormat]::xlCSV)  
    $excel.Quit()
    $sheet,$book,$excel|
    %{[void][Runtime.InteropServices.Marshal]::ReleaseComObject($_)}

    ご覧の通りこちらの方法だと定数値をコード中で参照することもできます。






    • 回答としてマーク Ruru 2011年6月23日 20:53
    2011年6月23日 15:14
    モデレータ
  • 回答ありがとうございました。

     

    2011年6月23日 20:53
  • > ExcelのCOMオブジェクトをそのまま呼ぶのではなく、C#などで使うためにあらかじめ
    >
    COMオブジェクトをラップした.NETオブジェクトを使うのも良いかと思います。

    たしか PowerShell では、New-Object -com で作成する COM コンポーネントにたいして PIA が存在する場合、PIA のインスタンスを勝手に作るんじゃなかったでしたっけ。

    help new-object をみると、-Strict をつけることで PIA がある場合にも生の COM オブジェクトが生成&取得できるようなことも書かれていますので、おそらくそのはず…。

    > 最後にCOMオブジェクトを格納した変数すべてにReleaseComObjectメソッドを使って開放してやる必要があります。

    スコープを抜けたところで自動的にやってくれたと思います。終了すれば~のくだりが、それを意図しているのかもしれませんが、Release を呼ぶのではなくて $null を代入するとか Remove-Variable を呼ぶとかいう表現が Powershell 的かもしれません。

     

    2011年8月11日 4:03
  • > たしか PowerShell では、New-Object -com で作成する COM コンポーネントにたいして PIA が存在する場合、PIA のインスタンスを勝手に作るんじゃなかったでしたっけ。

    確認したところ、そうなっていました。
    つまり回答に書いたコードにおける$excel変数に格納されているオブジェクトは、どちらも同じもの、ということになりますね。

    [Microsoft.Office.Interop.Excel.XlFileFormat]::xlCSV

    の部分も、$excel = New-Object -comobject Excel.ApplicationしておけばAdd-Typeせずとも呼べました。

    > help new-object をみると、-Strict をつけることで PIA がある場合にも生の COM オブジェクトが生成&取得できるようなことも書かれていますので、おそらくそのはず…。

    これは単にその場合エラーが発生するだけで、生のCOMオブジェクトを取得できるわけではないようです。

    > スコープを抜けたところで自動的にやってくれたと思います。終了すれば~のくだりが、 それを意図しているのかもしれませんが、Release を呼ぶのではなくて $null を代入するとか Remove-Variable を呼ぶとかいう表現が Powershell 的かもしれません。

    確認したところ、スコープを抜けても、$nullを代入してもCOMオブジェクトの参照は解放されず、プロセスは残ったままでした。
    しかしRemove-Variableコマンドレットを使って変数を消去すると参照も解放され、プロセスは終了しました。

     

    $sheet,$book,$excel|
    %{[void][Runtime.InteropServices.Marshal]::ReleaseComObject($_)}
    Remove-Variable sheet,book,excel
    と書いた方が手っ取り早いということになりますね。
    補足ありがとうございました。

     


    2011年8月11日 7:20
    モデレータ
  • さらに追試したところ、スコープを抜けたり$nullを代入しても「すぐには」解放されない、ということですね。
    GCが動いた時点で解放されるようです。

    つまり、

    $sheet=$book=$excel=$null
    [gc]::collect()

    でもいけるわけですね。まあRemove-Variableの方が楽なのでそちらがいいでしょうね。

    2011年8月11日 7:53
    モデレータ