none
PowerShellの変数の扱いについて。(同じ"文字列"なのに動作が違う) RRS feed

  • 質問

  • 前提・実現したい事
    ---
    XMLファイルの中から、任意のタグのみ抜き出したい。

    【方法】
    Excelに、抜き出したいタグを記入。(A1から下方向に向かって記入。A1~A10みたいな)
    PowerShellを使用し、「XMLファイル」と「Excelファイル」を読み込み、Excelファイル中に記載されているタグを、別名付けて保存したいです。

    【参照元XMLファイル】Original_XML.xml

    <?xml version="1.0" encoding="utf-8"?>
    <EXP_HogehogeWorkDataTable>
    	<DocumentElement>
    
    	<EXP_HogehogeWork id="EXP_HogehogeWork1">
    		<RecordId>00000055643</RecordId>
    		<Title>自己紹介1</Title>
    		<Name>田中太</Name>
    		<From>日本</From>
    		<BloodType>A</BloodType>
    		<Height>156cm</Height>
    		<BodyWeight>120kg</BodyWeight>
    		<LastUpdateDate>2015-04-20T11:13:59.000+09:00</LastUpdateDate>
    	</EXP_HogehogeWork>
    
    	<EXP_HogehogeWork id="EXP_HogehogeWork2">
    		<RecordId>00000925544</RecordId>
    		<Title>自己紹介2</Title>
    		<Name>砂糖一</Name>
    		<From>パリ</From>
    		<BloodType>B</BloodType>
    		<Height>185cm</Height>
    		<BodyWeight>56kg</BodyWeight>
    		<LastUpdateDate>2015-04-21T11:23:45.000+09:00</LastUpdateDate>
    	</EXP_HogehogeWork>
    
    	<EXP_HogehogeWork id="EXP_HogehogeWork3">
    		<RecordId>00000007048</RecordId>
    		<Title>自己紹介3</Title>
    		<Name>関克也</Name>
    		<From>ドイツ</From>
    		<BloodType>O</BloodType>
    		<Height>174cm</Height>
    		<BodyWeight>76kg</BodyWeight>
    		<LastUpdateDate>2015-04-21T20:12:02.000+09:00</LastUpdateDate>
    	</EXP_HogehogeWork>
    
    	<EXP_HogehogeWork id="EXP_HogehogeWork4">
    		<RecordId>00001611089</RecordId>
    		<Title>自己紹介4</Title>
    		<Name>山田花子</Name>
    		<From>イタリア</From>
    		<BloodType>AB</BloodType>
    		<Height>160cm</Height>
    		<BodyWeight>67kg</BodyWeight>
    		<LastUpdateDate>2015-04-23T13:23:08.000+09:00</LastUpdateDate>
    	</EXP_HogehogeWork>
    
    	</DocumentElement>
    </EXP_HogehogeWorkDataTable>

    【タグ一覧Excelファイル】XML_TagList.xlsx

    Excelの中身

    実際のソースコード
    ---

    <# 変数宣言 #>
    $XmlFilePath    = "C:\powershell\Original_XML.xml"  # "XML" 参照先
    $XmlSavePath    = "C:\powershell\Create_XML.xml"    # "XML" 保存先
    $ExcelFilePath  = "C:\powershell\XML_TagList"       # "Excel" 参照先
    $sheet_No       = 1                                 # シート番号
    $ex_GyouNo      = 1                                 # 取得対象:開始行番
    $ex_RetsuNo     = 1                                 # 取得対象:開始列番
    $ex_Alphabet    = "A"                               # "Excel" の「列A」を指定
    $TagName        = $null                             # 初期化
    
    <# 処理開始 #>
    try {
        # Excelオブジェクト作成
        $excel          = New-Object -ComObject Excel.Application
        $excel.Visible  = $false
    
        # 参照先Excel指定
        $book           = $excel.Workbooks.Open($ExcelFilePath)
    
        # シート名指定(番号で指定)
        $sheet          = $excel.Worksheets.Item($sheet_No)
    
        # 処理回数取得(A列より取得)
        $rows           = $sheet.UsedRange.Rows.Count
        foreach ( $max in $ex_Alphabet ) {
            $col = $excel.WorksheetFunction.CountIf($sheet.Range($max + "1:" + $max + $rows), "<>") -1
        }
    
        # 項目取得
        for ($i = 0; $i -lt $col; $i++) {
            $TagName += '"' + $sheet.Cells.Item($i + $ex_GyouNo, $ex_RetsuNo).Text + '"'
    
            <# 最後のタグ文字を取得したら終了 #>
            if ($i -eq $col -1) {       # 最後のループ処理か判断
                break                   # break:ループ処理終了
            }
            else {
                $TagName += "`,"        # タグ文字の最後にカンマ追加
            }
        }
    
        # Excelを閉じる
        $excel.Quit()
    }
    catch {
        write-host "info: $($_.Exception.Message)" -foregroundcolor red
    
    } finally {
        # null
        $excel, $book, $sheet | ForEach-Object{$_ = $null}
    
        # オブジェクトの破棄
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheet)
        [System.GC]::Collect()
    }
    
    # XMLファイル読み込み
    $xml = [xml](Get-Content $XmlFilePath)
    
    <#========== 問題部分 ==========#>
    
    # XMLタグ抜き出し
    $CustomXML = $xml.EXP_HogehogeWorkDataTable.DocumentElement |
        ForEach-Object {
            $_[$TagName].OuterXml
        }
    <#========== 問題部分 ==========#>
    
    # ファイル出力
    $CustomXML | Out-File $XmlSavePath

    ソースの説明としては、
    ①XML_TagList.xlsx から、タグ名を「変数:$TagName」に、【"RecordId","Name","From"】の形で取得。
    ②XMLファイルを読み込み、先程Excelから取得したタグ名を「変数:$TagName」指定し、取得。



    発生している問題・Error Message
    ---
    上記ソースコードを実行すると、エラーも出ず、別名でファイル保存が出来るのですが、作成したファイルを見てみると何も書かれていませんでした。

    問題部分 と囲ったコード部分を、

    <#========== 処理成功例 ==========#>
    
    # XMLタグ抜き出し Pattern1
    $CustomXML = $xml.EXP_HogehogeWorkDataTable.DocumentElement |
        ForEach-Object {
            $_["RecordId"].OuterXml
            $_["Name"].OuterXml
            $_["From"].OuterXml
        }
    
    # -------------------------------------------------------- #
    
    # XMLタグ抜き出し Pattern2
    $TagName = "RecordId","Name","From"
    $CustomXML = $xml.EXP_HogehogeWorkDataTable.DocumentElement |
        ForEach-Object {
            $_[$TagName].OuterXml
        }
    
    # -------------------------------------------------------- #
    
    # XMLタグ抜き出し Pattern3
    $CustomXML = $xml.EXP_HogehogeWorkDataTable.DocumentElement |
        ForEach-Object {
            $_["RecordId","Name","From"].OuterXml
        }
    <#========== 処理成功例 ==========#>

    の様に書くと正常に取得出来ます。

    * パターン1では、タグ名を手入力により指定。
    * パターン2では、変数にタグ名を入れ、変数を指定。
    * パターン3では、パターン1の様に1ずつ記載では無く、まとめて指定。


    試した事
    ---
    「変数に文字列を代入した場合」 と 「Excelから取得した値(文字)」 は、型が変わるのかと思い

    [string]$TagName
    の様に、文字型指定してもダメでした。

    パターン2の様に、変数に文字列を代入した場合は取得が出来、
    Excelから取得した場合は失敗する理由が分かりません。
    どなたか、ご教示頂ければと思います。
    よろしくお願い致します。

    補足情報(言語/FW/ツール等のVersion)
    ---
    ・PowerShell ver5.0
    ・Windows 7
    ・Excel拡張子(主に扱うのは、".xls" と ".xlsx")
    2017年6月22日 1:54

回答

  • チャブーンです。

    #ぱっとみですが

    成功例の「$TagName = "RecordId","Name","From"」ですが、成功すると$TagNameはArray型になっていることがわかります。

    で当初のソースですが、「$TagName = $null」ということで非Arrayで定義していますね。その後の「$TagName += '"' + $sheet.Cells.Item($i + $ex_GyouNo, $ex_RetsuNo).Text + '"'」が想定通りになっていないのではないでしょうか。

    ひとまず$TagName = @()で定義し、Arrayとして各タグを加えていってはどうでしょうか。

    追記:$TagNameをArrayで定義した場合、タグの追加は「$TagName += $sheet.Cells.Item($i + $ex_GyouNo, $ex_RetsuNo).Text」でいいかと思います。


    フォーラムは有償サポートとは異なる「コミュニティ」です。フォーラムでご質問頂くにあたっての注意点 をご一読のうえ、お楽しみください。


    2017年6月22日 2:11

すべての返信

  • チャブーンです。

    #ぱっとみですが

    成功例の「$TagName = "RecordId","Name","From"」ですが、成功すると$TagNameはArray型になっていることがわかります。

    で当初のソースですが、「$TagName = $null」ということで非Arrayで定義していますね。その後の「$TagName += '"' + $sheet.Cells.Item($i + $ex_GyouNo, $ex_RetsuNo).Text + '"'」が想定通りになっていないのではないでしょうか。

    ひとまず$TagName = @()で定義し、Arrayとして各タグを加えていってはどうでしょうか。

    追記:$TagNameをArrayで定義した場合、タグの追加は「$TagName += $sheet.Cells.Item($i + $ex_GyouNo, $ex_RetsuNo).Text」でいいかと思います。


    フォーラムは有償サポートとは異なる「コミュニティ」です。フォーラムでご質問頂くにあたっての注意点 をご一読のうえ、お楽しみください。


    2017年6月22日 2:11
  • ご回答ありがとうございます。

    記載し忘れ申し訳ございませんが、既に配列定義では試しております。

    配列定義しても、結果は空のXMLファイルが出来てしまいます。

    2017年6月22日 2:28
  • チャブーンです。

    すみませんが、「うまくいかなかった」実際のソースを教えてください。判断できないので。

    また、うまくいかない場合PowerShellをデバックする方法があると思います。PowerShell ISEを使って、「       $TagName += '"' + $sheet.Cells.Item($i + $ex_GyouNo, $ex_RetsuNo).Text + '"'」あたりの動作をブレークポイントに設定して、確認することになると思います。

    私としては、$TagNameはArrayが必要だと思っていまして(実際のところは関数のリファレンスを見る必要がありますが)、カンマ連結によるString型にすることは現時点は考えていないのですが...。そこのところは認識があっていますでしょうか。


    フォーラムは有償サポートとは異なる「コミュニティ」です。フォーラムでご質問頂くにあたっての注意点 をご一読のうえ、お楽しみください。

    2017年6月22日 2:36
  • <# 変数宣言 #>
    $XmlFilePath    = "C:\powershell\Original_XML.xml"  # "XML" 参照先
    $XmlSavePath    = "C:\powershell\Create_XML.xml"    # "XML" 保存先
    $ExcelFilePath  = "C:\powershell\XML_TagList"       # "Excel" 参照先
    $sheet_No       = 1                                 # シート番号
    $ex_GyouNo      = 1                                 # 取得対象:開始行番
    $ex_RetsuNo     = 1                                 # 取得対象:開始列番
    $ex_Alphabet    = "A"                               # "Excel" の「列A」を指定
    $TagName        = @()                               # 配列
    
    <# 処理開始 #>
    try {
        # Excelオブジェクト作成
        $excel          = New-Object -ComObject Excel.Application
        $excel.Visible  = $false
    
        # 参照先Excel指定
        $book           = $excel.Workbooks.Open($ExcelFilePath)
    
        # シート名指定(番号で指定)
        $sheet          = $excel.Worksheets.Item($sheet_No)
    
        # 処理回数取得(A列より取得)
        $rows           = $sheet.UsedRange.Rows.Count
        foreach ( $max in $ex_Alphabet ) {
            $col = $excel.WorksheetFunction.CountIf($sheet.Range($max + "1:" + $max + $rows), "<>") -1
        }
    
        # 項目取得
        for ($i = 0; $i -lt $col; $i++) {
            $TagName += '"' + $sheet.Cells.Item($i + $ex_GyouNo, $ex_RetsuNo).Text + '"'
    
            <# 最後のタグ文字を取得したら終了 #>
            if ($i -eq $col -1) {       # 最後のループ処理か判断
                break                   # break:ループ処理終了
            }
            else {
                $TagName += "`,"        # タグ文字の最後にカンマ追加
            }
        }
    
        # Excelを閉じる
        $excel.Quit()
    }
    catch {
        write-host "info: $($_.Exception.Message)" -foregroundcolor red
    
    } finally {
        # null
        $excel, $book, $sheet | ForEach-Object{$_ = $null}
    
        # オブジェクトの破棄
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheet)
        [System.GC]::Collect()
    }
    
    # XMLファイル読み込み
    $xml = [xml](Get-Content $XmlFilePath)
    
    # XMLタグ抜き出し
    $CustomXML = $xml.EXP_HogehogeWorkDataTable.DocumentElement |
        ForEach-Object {
            $_[$TagName].OuterXml
        }
    
    # ファイル出力
    $CustomXML | Out-File $XmlSavePath

    こちらのソースコードになります。

    最初に投稿した所と変わっているのが、「$TagName = $null → $TagName = @()」に変わっているだけです。

    カンマ連結によるString型にすることは現時点は考えていないのですが...。そこのところは認識があっていますでしょうか。

    申し訳ございませんが、上記はどんな意味でしょうか?

    自分としては、

    「Excelのセルの値」

    「連結するカンマ」・・・全てを文字列として取得し、最後にどんどんくっつける(+=)やり方をしたいのですが・・・

    ---------------

    デバックも試してはおりますが、その所にブレークポイントを設定し、実行すると落ちてしまいます。

    使用しているのは、「VSCODE」です。

    • 編集済み mie.8 2017年6月22日 3:39
    2017年6月22日 3:37
  • $_["RecordId","Name","From"].OuterXml

    この$_の添え字は、実のところ文字列の配列です。

    $a = "RecordId","Name","From"
    $a.GetType()

    とやるとArrayが返りますし、$aを評価すれば3行にわたって各行にRecordIdやらNameやらが表示されます。

    翻って、$TagNameはGetType()ではStringが返るし直接評価すれば1行にだらっと表示されるでしょう。

    つまり、$TagNameを@()で空の配列としてまず初期化し、必要な(Excelから読み取った)要素名を順次 += で配列に追加していくのが良いでしょう(PowerShellでは、配列は可変長で、+=で要素を追加していきます)。もちろん、二重引用符記号やらカンマやらをわざわざ追加する意味はありません。

    2017年6月22日 4:13
  • チャブーンです。

    ひとまず問題解決されたようでよかったです。

    https://teratail.com/questions/81331

    Arrayとして要素を追加する場合、<配列変数> += <配列要素>だけでよく、それ以外の項目("等)の入力は不要です(逆に失敗します)。


    フォーラムは有償サポートとは異なる「コミュニティ」です。フォーラムでご質問頂くにあたっての注意点 をご一読のうえ、お楽しみください。

    2017年6月22日 4:28