コンソールアプリケーションのPowershellとキリル文字(更新)

開発プロセスでは、多くの場合、Powershellスクリプトからコンソールアプリケーションを実行する必要があります。 何がもっと簡単だろうか?



#test.ps1 & $PSScriptRoot\ConsoleApp.exe
      
      













コマンドライン、PowerShell、およびPowerShell ISEからコンソールアプリケーションを起動したときのコンソールアプリケーションの動作を調べます。



実行結果




PowerShell ISEでは、1251エンコーディングでの出力を想定しているため、エンコーディングに問題がありました。Googleを使用して、[Console] :: OutputEncodingを使用して、Powershellパイプラインを介して2つの解決策を見つけます。 最初のソリューションを使用します。



test2.ps1
 $ErrorActionPreference = "Stop" function RunConsole($scriptBlock) { $encoding = [Console]::OutputEncoding [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("cp866") try { &$scriptBlock } finally { [Console]::OutputEncoding = $encoding } } RunConsole { & $PSScriptRoot\ConsoleApp1.exe }
      
      







実行結果




コマンドラインではすべて問題ありませんが、ISEにはエラーがあります。 例外設定「OutputEncoding」:「ハンドルが無効です。」 繰り返しになりますが、Googleを手に入れると、最初の結果で解決策が見つかりました。コンソールアプリケーションを実行してコンソールを作成する必要があります。 さて、試してみましょう。



test3.ps1
 $ErrorActionPreference = "Stop" function RunConsole($scriptBlock) { #   "" : Exception setting "OutputEncoding": "The handle is invalid." & cmd /c ver | Out-Null $encoding = [Console]::OutputEncoding [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("cp866") try { &$scriptBlock } finally { [Console]::OutputEncoding = $encoding } } RunConsole { & $PSScriptRoot\ConsoleApp1.exe }
      
      







実行結果




すべてが美しく、すべてが機能します。 私の最後の投稿を読んだ人は、WinRMが多くのスリルをもたらしていることに気付きました。 WinRMを使用してテストを実行してみましょう。 実行するには、次のスクリプトを使用します。



remote1.ps1
 param($script) $ErrorActionPreference = "Stop" $s = New-PSSession "." try { $path = "$PSScriptRoot\$script" Invoke-Command -Session $s -ScriptBlock { &$using:path } } finally { Remove-PSSession -Session $s }
      
      







実行結果




何かがおかしかった。 コンソール作成ソリューションが機能しません。 前に、エンコードの問題に対する2つの解決策を見つけました。 2番目のものを試してみましょう:



test4.ps1
 $ErrorActionPreference = "Stop" #$VerbosePreference = "Continue" function RunConsole($scriptBlock) { function ConvertTo-Encoding ([string]$From, [string]$To) { Begin { $encFrom = [System.Text.Encoding]::GetEncoding($from) $encTo = [System.Text.Encoding]::GetEncoding($to) } Process { $bytes = $encTo.GetBytes($_) $bytes = [System.Text.Encoding]::Convert($encFrom, $encTo, $bytes) $encTo.GetString($bytes) } } Write-Verbose "RunConsole: Pipline mode" &$scriptBlock | ConvertTo-Encoding cp866 windows-1251 } RunConsole { & $PSScriptRoot\ConsoleApp1.exe }
      
      







実行結果




ISEおよびWinRMを使用すると、ソリューションは機能しますが、コマンドラインとシェルを使用します-いいえ。

これら2つの方法を組み合わせる必要があり、問題は解決されます!



test5.ps1
 $ErrorActionPreference = "Stop" #$VerbosePreference = "Continue" function RunConsole($scriptBlock) { if([Environment]::UserInteractive) { #   "" : Exception setting "OutputEncoding": "The handle is invalid." & cmd /c ver | Out-Null $encoding = [Console]::OutputEncoding [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("cp866") try { Write-Verbose "RunConsole: Console.OutputEncoding mode" &$scriptBlock return } finally { [Console]::OutputEncoding = $encoding } } function ConvertTo-Encoding ([string]$From, [string]$To) { Begin { $encFrom = [System.Text.Encoding]::GetEncoding($from) $encTo = [System.Text.Encoding]::GetEncoding($to) } Process { $bytes = $encTo.GetBytes($_) $bytes = [System.Text.Encoding]::Convert($encFrom, $encTo, $bytes) $encTo.GetString($bytes) } } Write-Verbose "RunConsole: Pipline mode" &$scriptBlock | ConvertTo-Encoding cp866 windows-1251 } RunConsole { & $PSScriptRoot\ConsoleApp1.exe }
      
      







実行結果




問題は解決されたようですが、出力をstdErrorに追加することにより、コンソールアプリケーションの調査と複雑化を続けます。



実行結果




さらに楽しくなっています:) ISEでは、スクリプトの実行が途中で中断され、WinRMを介して中断されただけでなく、stdErrからメッセージを読み取ることもできません。 最初のステップは、スクリプトから起動されたアプリケーションを停止する問題を解決することです。このため、アプリケーションを開始する前に、グローバル変数$ ErrorActionPreferenceの値を変更します。



test7.ps1
 $ErrorActionPreference = "Stop" #$VerbosePreference = "Continue" function RunConsole($scriptBlock) { if([Environment]::UserInteractive) { #   "" : Exception setting "OutputEncoding": "The handle is invalid." & cmd /c ver | Out-Null $encoding = [Console]::OutputEncoding [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("cp866") try { Write-Verbose "RunConsole: Console.OutputEncoding mode" $prevErrAction = $ErrorActionPreference $ErrorActionPreference = "Continue" try { &$scriptBlock return } finally { $ErrorActionPreference = $prevErrAction } } finally { [Console]::OutputEncoding = $encoding } } function ConvertTo-Encoding ([string]$From, [string]$To) { Begin { $encFrom = [System.Text.Encoding]::GetEncoding($from) $encTo = [System.Text.Encoding]::GetEncoding($to) } Process { $bytes = $encTo.GetBytes($_) $bytes = [System.Text.Encoding]::Convert($encFrom, $encTo, $bytes) $encTo.GetString($bytes) } } Write-Verbose "RunConsole: Pipline mode" $prevErrAction = $ErrorActionPreference $ErrorActionPreference = "Continue" try { &$scriptBlock | ConvertTo-Encoding cp866 windows-1251 return } finally { $ErrorActionPreference = $prevErrAction } } RunConsole { & $PSScriptRoot\ConsoleApp2.exe } Write-Host "ExitCode = $LASTEXITCODE"
      
      







実行結果




-ErrorActionパラメーターの存在を知っている人向け
error.cmd

 echo error message 1>&2
      
      





errorActionTest.ps1

 #error.cmd #echo error message 1>&2 #errorActionTest.ps1 $ErrorActionPreference = "Stop" Write-Host "before" Invoke-Expression -ErrorAction SilentlyContinue -Command $PSScriptRoot\error.cmd Write-Host "after"
      
      





そのようなスクリプトを実行した結果はどうなりますか?



2番目の手順は、WinRMを使用してリモート起動スクリプトを終了し、クラッシュしないようにすることです。



remote2.ps1
 param($script) $ErrorActionPreference = "Stop" $s = New-PSSession "." try { $path = "$PSScriptRoot\$script" $err = @() $r = Invoke-Command -Session $s -ErrorAction Continue -ErrorVariable err -ScriptBlock ` { $ErrorActionPreference = "Stop" & $using:path | Out-Host return $true } if($r -ne $true) { Write-Error "The remote script was completed with an error" } if($err.length -ne 0) { Write-Warning "Error occurred on remote host" } } finally { Remove-PSSession -Session $s }
      
      







実行結果




そして最も難しいのは、stdErrを介して生成されたメッセージを修正すると同時に、ログ内の位置を変更しないことです。 この問題を解決する過程で、同僚はwin API関数AllocConsoleを使用して自分でコンソールを作成することを提案しました。



test8.ps1
 $ErrorActionPreference = "Stop" #$VerbosePreference = "continue" $consoleAllocated = [Environment]::UserInteractive function AllocConsole() { if($Global:consoleAllocated) { return } &cmd /c ver | Out-Null $a = @' [DllImport("kernel32", SetLastError = true)] public static extern bool AllocConsole(); '@ $params = New-Object CodeDom.Compiler.CompilerParameters $params.MainClass = "methods" $params.GenerateInMemory = $true $params.CompilerOptions = "/unsafe" $r = Add-Type -MemberDefinition $a -Name methods -Namespace kernel32 -PassThru -CompilerParameters $params Write-Verbose "Allocating console" [kernel32.methods]::AllocConsole() | Out-Null Write-Verbose "Console allocated" $Global:consoleAllocated = $true } function RunConsole($scriptBlock) { AllocConsole $encoding = [Console]::OutputEncoding [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("cp866") $prevErrAction = $ErrorActionPreference $ErrorActionPreference = "Continue" try { & $scriptBlock } finally { $ErrorActionPreference = $prevErrAction [Console]::OutputEncoding = $encoding } } RunConsole { & $PSScriptRoot\ConsoleApp2.exe } Write-Host "ExitCode = $LASTEXITCODE"
      
      













powershellがstdErrに追加する情報をまだ取り除くことができませんでした。



この情報が私だけでなく役に立つことを願っています! :)



アップデート1

一部の使用シナリオでは、スクリプト実行の結果が発行される追加のコンソールが作成されました。 test8.ps1スクリプトが修正されました。



アップデート2

この記事の多くのコメンテーターは文字セットとエンコードの概念を混同しているため、この記事がコンソールと呼び出されたアプリケーションのエンコードの正確な不一致の問題を解決するという事実に再び注目したいと思います。



test8.ps1スクリプトからわかるように、エンコーディングは静的プロパティ[Console] :: OutputEncodingで指定されており、その中のUnicodeファミリのエンコーディングの1つを示すことを誰も気にしません。

 [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("utf-8")
      
      





ただし、標準のWindowsコンソール(別名cmd.exe)でスクリプトを機能させるには、コンソールフォントを標準の「ラスタフォント」から「コンソラス」または「ルシダコンソール」に変更する必要があります。 これらのスクリプトを自分のワークステーションまたはサーバーでのみ使用した場合、そのような変更は許可されますが、ソリューションを顧客に配布する必要があるため、サーバーのシステム設定に介入する権利はありません。 このため、コンソールではエンコードがデフォルトで構成されているため、cp866がスクリプトで使用されます。



All Articles