LoginSignup
3
2

More than 5 years have passed since last update.

PowerShellで起動演算子(&)を利用した時にハマったこととその回避策

Last updated at Posted at 2016-10-24

前提環境


> &PSVersionTable

Name                      Value
----                      -----
CLRVersion                2.0.50727.5485
BuildVersion              6.1.7601.17514
PSVersion                 2.0
WSManStackVersion         2.0
PSCompatibleVersions      {1.0, 2.0}
SerializationVersion      1.1.0.1
PSRemotingProtocolVersion 2.1

やりたいこと

起動演算子(&)経由の呼び出しであってもメッセージをそのまま出力したい

事象

起動演算子(&)を利用した時、メッセージの内容によっては正しく外部コマンドへ渡されない

例:
&でeventcreate.exeに引数としてメッセージを渡すscript

eventcreateTest.ps1

$cmd      = "C:¥WINDOWS¥system32¥eventcreate.exe"
$severity = "ERROR"
$id       = "1000"
$kind     = "APPLICATION"
$msg      = args[0]

$ret      = & $cmd /T $severity /ID $id /L $kind /D $msg

if ($LASTEXITCODE -ne 0) {
  throw "error! $LASTEXITCODE"
}

上記のとき、$msgが


  "abcd"    -> abcd
  "ab cd"   -> ab cd
  "ab cd¥"  -> ab cd"     ## "がエスケープされてエラーが返却されて欲しい
  "ab`tcd¥" -> ab[tab]cd" ## 同上
  "abcd¥"   -> abcd¥      ## ?!
  "a¥bcd"   -> a¥bcd
  "a¥b cd"  -> a¥b cd

  ## " -> ' でも結果は同じ

動きだけ見ているとC#コンパイラの解釈に沿った動きに見える
参考: http://www.msdn.microsoft.com/ja-jp/library/78f4aasd(VS.80).aspx

回避策

こんな処理を入れる


function escape($msg) {
  if($msg.Contains(" ") -or $msg.Contains("`t")) {
    $match      = [Regex]::Match($msg, "¥¥+$")
    $firstPart  = $msg
    $secondPart = ""
    if($match.Success) {
      ## 末尾の¥の塊とそれ以外にわける
      $firstPart  = $msg.SubString(0, $match.Index)
      $secondPart = $msg.SubString($match.Index)

      ## 末尾の¥を倍にする
      $secondPart = $secondPart -replace "¥¥", "¥¥"
    }
    ## メッセージ中の"や¥を文字列として表示したい場合のescape
    $firstPart = $firstPart -replace "¥¥`"", "¥¥¥`""
    $firstPart = $firstPart -replace "(?<!¥¥)`"", `""
    $msg       = $firstPart + $secondPart
  }
  return $msg
}

上記でコマンド上の " は ¥" 末尾 n¥ は 2n¥ でエスケープしたメッセージとして渡される
あと ¥" や " も文字列としてエスケープされる。
正規表現の先読み/後読みって便利

結果

eventcreateTest.ps1

function escape($msg) {
  if($msg.Contains(" ") -or $msg.Contains("`t")) {
    $match      = [Regex]::Match($msg, "¥¥+$")
    $firstPart  = $msg
    $secondPart = ""
    if($match.Success) {
      ## 末尾の¥の塊とそれ以外にわける
      $firstPart  = $msg.SubString(0, $match.Index)
      $secondPart = $msg.SubString($match.Index)

      ## 末尾の¥を倍にする
      $secondPart = $secondPart -replace "¥¥", "¥¥"
    }
    ## メッセージ中の"や¥を文字列として表示したい場合のescape
    $firstPart = $firstPart -replace "¥¥`"", "¥¥¥`""
    $firstPart = $firstPart -replace "(?<!¥¥)`"", `""
    $msg       = $firstPart + $secondPart
  }
  return $msg
}

$cmd      = "C:¥WINDOWS¥system32¥eventcreate.exe"
$severity = "ERROR"
$id       = "1000"
$kind     = "APPLICATION"
$msg      = escape(args[0])

$ret      = & $cmd /T $severity /ID $id /L $kind /D $msg

if ($LASTEXITCODE -ne 0) {
  throw "error! $LASTEXITCODE"
}



  "abcd"    -> abcd
  "ab cd"   -> ab cd
  "ab cd¥"  -> ab cd¥     ## OK
  "ab`tcd¥" -> ab[tab]cd¥ ## OK
  "abcd¥"   -> abcd¥      ## OK
  "a¥bcd"   -> a¥bcd
  "a¥b cd"  -> a¥b cd

:ok:

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2