🏃‍♀️

[Code Runner]UTF-8 BOM付きのPowerShellスクリプトを実行する設定

2023/11/22に公開

概要

Markdownファイル内のコード内容だけで正常動作するか検証したく、VS Codeに拡張機能のCode Runnerを導入しました。

実際に複数行のPowerShellコードを範囲選択して動かしてみると、文字化けしてエラーが発生してしまいました。
今回、この問題を解決する方法が見つかったので紹介します。

この記事のターゲット

  • VS Codeユーザーの方
  • Code Runnerについて知りたい方
  • Code RunnerでPowerShell実行時に文字化けしてエラーが発生する問題を解決したい方

環境

Windows & PowerShell

PowerShell Coreをインストール済み。

Windows 10 & PowerShell 7.3.8
PS C:\Users\XXXX> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      7.3.8
PSEdition                      Core
GitCommitId                    7.3.8
OS                             Microsoft Windows 10.0.19045
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

PS C:\Users\XXXX>

VS Code

コピー用
code -v
VS Code 1.83.1
PS C:\Users\XXXX> code -v
1.83.1
f1b07bd25dfad64b0167beb15359ae573aecd2cc
x64
PS C:\Users\XXXX>

Code Runner

コピー用
code --list-extensions --show-versions
Code Runner 0.12.1
PS C:\Users\XXXX> code --list-extensions --show-versions
formulahendry.code-runner@0.12.1

 ~ 省略 ~
 
PS C:\Users\XXXX>

なぜCode Runnerを導入したのか

冒頭でも記載していますが、Markdown内で書いたコードの動作検証を簡略化したくなり導入しました。

この記事を投稿しているプラットフォームZenn.devではMarkdown記法を使用しています。
Markdownファイル内にプログラムコードを書いた後、そのコードが正常に動くか検証する為、
対応する言語ファイル(たとえばPowerShellスクリプトの場合、*.ps1)を個別に書き出した後、実行していました。

一度で動作検証が終われば良いですが試行錯誤すると、どうしても何度も繰り返して作業する事になり、
個別に書き出して実行する事が面倒に感じていました。

ネットで探してみると、VS Codeの拡張機能であるCode Runnerを使用する事で、
マウス・キーボードで範囲選択したコードだけの実行する事ができるとの事で導入しました。

実際に動かしてみるとエラーが発生

Code Runnerを使い動かしてみた結果、範囲選択したコードの実行でエラーが発生しました。
エラー内容をみると文字化けしている事がわかります。

Code Runnerの範囲選択したコードを実行したが出力情報でエラー。エラー内容が文字化けしている。
画像:範囲選択したコードの実行でエラー

Code Runnerの選択範囲のコードを実行するもエラーが発生(出力にてエラー)
[Running] powershell -ExecutionPolicy ByPass -File "C:\Users\ADMINI~1\AppData\Local\Temp\tempCodeRunnerFile.ps1"
�����ꏊ C:\Users\Administrator\AppData\Local\Temp\tempCodeRunnerFile.ps1:21 ����:39
+     [System.String]$prompt_message = '参�Eするフォルダーを�E力してください、E
+                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
���܂��̓X�e�[�g�����g�̃g�[�N�� '参�Eするフォルダーを�E力してください、E' ���g�p�ł��܂���B
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : UnexpectedToken
 

[Done] exited with code=1 in 3.738 seconds
  • 範囲選択したコード実行(Select Language To Run)
    ショートカットキー [Ctrl + Alt + J]
     
    実行したい部分を範囲選択してショートカットキーを入力。表示された言語のリストの中から“powershell”を選択すると実行できた。

    Code Runnerで範囲選択したコードを実行すると表示される言語リスト。Markdownファイル上にあるPowerShellのコードを範囲選択している為、「powershell」を選択
    画像:Code Runnerの範囲選択したコードを実行するモードで表示される言語リスト

詳細情報:範囲選択で実行したコード < クリックで折りたたみが開く >
Markdownファイル内にあったPowerShellのコード
#################################################################################
# 処理名 |InputForm
# 機能  |対象フォルダーを指定する入力フォーム
#--------------------------------------------------------------------------------
# 戻り値 |Boolean(True: OKボタン、False: OKボタン以外)
# 引数  |target_folder: 対象フォルダー
#################################################################################
Function InputForm {
    Param (
        [System.String]$target_folder
    )

    # アセンブリ読み込み(フォーム用)
    Add-Type -AssemblyName System.Windows.Forms
    Add-Type -AssemblyName System.Drawing

    [System.String[]]$input_parameters = @()

    # フォームの作成
    [System.String]$prompt_title = 'タイトル:対象フォルダーを入力'
    [System.String]$prompt_message = '参照するフォルダーを入力してください。'
    [System.Windows.Forms.Form]$form = New-Object System.Windows.Forms.Form
    $form.Text = $prompt_title
    [System.UInt32[]]$form_size = @(550, 300)
    $form.Size = New-Object System.Drawing.Size($form_size[0],$form_size[1])
    $form.StartPosition = 'CenterScreen'
    $form.MaximizeBox = $false
    $form.MinimizeBox = $false

    [System.String]$font_family = 'MS ゴシック'
    [System.Int32]$font_size = 12

    # ラベル作成
    [System.Windows.Forms.Label]$label = New-Object System.Windows.Forms.Label
    $label.Location = New-Object System.Drawing.Point(20, 20)
    $label.Size = New-Object System.Drawing.Size(350, 50)
    $label.Text = $prompt_message
    [System.Drawing.Font]$font = New-Object System.Drawing.Font($font_family, $font_size)
    $label.Font = New-Object System.Drawing.Font($font_family, $font_size)

    # テキストボックスの作成
    [System.Windows.Forms.TextBox]$textbox = New-Object System.Windows.Forms.TextBox
    $textbox.Location = New-Object System.Drawing.Point(40, 80)

    # 100の場合
    $textbox.Size = New-Object System.Drawing.Size(350, 100)

    $textbox.Text = $target_folder
    $textbox.Font = New-Object System.Drawing.Font($font_family, $font_size)

    # 参照ボタンの作成
    [System.Windows.Forms.Button]$btnRefer = New-Object System.Windows.Forms.Button
    $btnRefer.Location = New-Object System.Drawing.Point(420, 80)
    $btnRefer.Size = New-Object System.Drawing.Size(75,25)
    $btnRefer.Text = '参照'

    # OKボタンの作成
    [System.Windows.Forms.Button]$btnOkay = New-Object System.Windows.Forms.Button
    $btnOkay.Location = New-Object System.Drawing.Point(355, 210)
    $btnOkay.Size = New-Object System.Drawing.Size(75,30)
    $btnOkay.Text = 'OK'
    $btnOkay.DialogResult = [System.Windows.Forms.DialogResult]::OK

    # Cancelボタンの作成
    [System.Windows.Forms.Button]$btnCancel = New-Object System.Windows.Forms.Button
    $btnCancel.Location = New-Object System.Drawing.Point(440,210)
    $btnCancel.Size = New-Object System.Drawing.Size(75,30)
    $btnCancel.Text = 'キャンセル'
    $btnCancel.DialogResult = [System.Windows.Forms.DialogResult]::Cancel

    # ボタンの紐づけ
    $form.AcceptButton = $btnOkay
    $form.CancelButton = $btnCancel

    # フォームに紐づけ
    $form.Controls.Add($label)
    $form.Controls.Add($textbox)
    $form.Controls.Add($btnRefer)
    $form.Controls.Add($btnOkay)
    $form.Controls.Add($btnCancel)

    # フォーム表示
    [System.Boolean]$is_Selected = ($form.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK)
    $form = $null

    return $is_Selected
}
# 実行
InputForm 'D:\Downloads'

補足情報:Code Runnerでアクティブファイルを実行するモードあり

ちなみにCode Runnerでは範囲選択したコードの実行だけでなく、アクティブファイルを実行できるモードもあります。
試しに実行していみるとアクティブファイルがMarkdownファイル(拡張子は*.md)だった為、、Code language not supported or defined.というメッセージが出ました。
Markdownファイルはプログラム実行用の形式ではないので想定通り実行できませんでした。

  • コードの実行(Run Code)
    ショートカットキー [Ctrl + Alt + N]

  • 実行結果
    アクティブファイルがMarkdownファイルだと実行できずメッセージが表示
    画像:メッセージ“Code language not supported or defined.”

Code Runnerでは初期設定されている主流の言語以外にも個別に追加する事が可能です。

範囲選択の実行で発生した文字化け表示とエラーの対応

Code Runnerの出力先をターミナルに設定(必須作業)

調べた結果、こちらで紹介されているCode Runnerの出力先をターミナルに変更する事で、
文字化けが解消されたという記事が見つかりました。

以下が詳しい設定方法です。

  1. VS Codeの設定画面を開く
    ショートカットキー操作 :Ctrl + ,(カンマ)
    画面操作 :ファイル(F)ユーザー設定(P)設定(S)を選択
  2. 設定で検索欄でcode-runner run in terminalと入力
  3. 設定項目「Code-runner: Run In Terminal」をチェックする
    Code Runnerの設定で「Code-runner: Run In Terminal」にチェックを入れる事で実行結果をターミナルに出力するよう変更
    画像:Code Runnerの設定で「Code-runner: Run In Terminal」にチェックを入れた状態

設定変更後、文字化けの度合いは少なくなったように見えるが、状況は変わらず文字化けとエラーが発生。
Code Runnerの設定でターミナルに出力するよう切り替えるも、状況は変わらず文字化けとエラーが発生する
画像:出力からターミナルに変更して選択範囲のコードを実行するも文字化けとエラーが発生

Code Runnerの選択範囲のコードを実行するもエラーが発生(ターミナルでエラー)
PS C:\Users\Administrator> powershell -ExecutionPolicy ByPass -File "c:\Users\Administrator\Downloads\tempCodeRunnerFile.ps1"
発生場所 C:\Users\Administrator\Downloads\tempCodeRunnerFile.ps1:21 文字:39
+     [System.String]$prompt_message = '蜿ら・縺吶k繝輔か繝ォ繝繝シ繧貞・蜉帙@縺ヲ縺上□縺輔>縲・
+                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
式またはステートメントのトークン '蜿ら・縺吶k繝輔か繝ォ繝繝シ繧貞・蜉帙@縺ヲ縺上□縺輔>縲・' を使用できません。
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : UnexpectedToken
 
PS C:\Users\Administrator> 

変わらず文字化けとエラーが発生しましたが最終的な結果、ここでの「 出力先をターミナルに設定 」 と 後述する「 Code RunnerでPowerShellの個別設定を追加 」 の2件あわせて対応した事により解決しました。

その為、ここで対応した「 出力先をターミナルに設定 」はそのままの状態で次項の作業に移ってください。

調査方法(私の環境で通常とは異なる点を洗い出し)

ネットの情報を元に一般的な環境と私の環境で異なる部分をあらためて洗い出した結果、
一般的な環境と違う点は、PowerShellスクリプトファイルをUTF-8 BOM付きで保存しているという点でした。

  • 一般的な環境
    通常PowerShellスクリプトは文字コードがUTF-8の環境で動作する

  • 私の環境の場合
    マルチバイト(日本語)を含む文字列を取り扱う為、PowerShellスクリプトファイル(*.ps1)をUTF-8 BOM付きで保存している。

    補足情報:なぜUTF-8 BOMで保存した方がよいのか < クリックで折りたたみが開く >

    今回、作成したPowerShellスクリプトは、ASCII文字以外(つまり日本語)が含まれている。
    PowerShellでは、ASCII文字以外の文字列が含まれていると自動的にANSI(日本語版Windows OSであれば、Shift-JISの事を指す)として処理され文字化けする。メッセージ内容の文字化けぐらいであれば動作に問題はないが、文字化けの度合いによっては動作しない場合もある。
    上記の回避策として日本語を含むPowerShellスクリプトは、文字コードを“UTF-8 BOM付き”で保存する事により、
    文字コードのデータがファイルに付与され文字化けやエラーがなく動作する。

この事をふまえてCode Runnerで実行される一時ファイル「tempCodeRunnerFile.ps1」を確認すると文字コードが「UTF-8」である事が判明。

原因

PowerShellの仕様動作から見ると、マルチバイト(日本語)がないPowerShellはUTF-8で正常動作する。
しかし、今回のコードではコメント行やフォームの設定値などで日本語があった。

通常はマルチバイトを含むPowerShellスクリプトはUTF-8 BOM付きで保存し実行する事で正常動作するが、
今回のCodeRunner経由での実行では、拡張機能が実行する為の一時ファイルの文字コードがUTF-8(BOMなし)だった。

CodeRunnerで実行した際、BOM情報がなくPowerShell側で文字コードが判別できなかった事と、
マルチバイトの文字(日本語)が含まれていた事でPowerShellが自動でShift-JISとして実行されてしまった為、文字化けが発生。
最終的にエラーが発生してしまったという事がわかった。

文字だけだとわかりにくいと思うので箇条書きにすると下記の流れでエラーが発生していた事がわかる。

  1. Markdownファイル内のコードを範囲選択してCode Runnerで実行
    範囲選択したコードには、日本語が含まれた状態
  2. (Code Runnerで自動)で範囲選択部分を抜き出し UTF-8 のps1ファイルを生成
  3. (Code Runnerで自動)生成したps1ファイルをPowerShellで実行
  4. (PowerShellで自動)BOM付きでないので文字コードが判別できないが日本語を含むps1ファイルである為 Shift-JIS で実行
  5. << エラーが発生 >>
    文字コード UTF-8のps1ファイルをShift-JISとして実行してしまった為 、日本語が文字化けしエラーが発生

Code RunnerでPowerShellの個別設定を追加(必須作業)

Code Runnerの設定でPowerShellを実行する際に個別の処理を追加する。
具体的に追加する内容は下記の通り。

  1. Code Runner実行時にターミナルで動作するよう変更
    前述に記載した必須作業が実施済みである事。
  2. 実行時、一時的に作成されるファイルをUTF-8 BOM付きに変換
    一時的に作成されるUTF-8のファイル「tempCodeRunnerFile.ps1
    上記をUTF8 BOMに変換したファイル 「tempCodeRunnerFile_utf8bom.ps1
    なお、変換したファイルの保存先は一時フォルダー「$Env:TEMPC:\Users\ユーザー名\AppData\Local\Temp) 」に保存する。
  3. 変換後、Code Runnerで一時的に作成されたUTF-8のファイルを削除
  4. UTF-8 BOMに変換したファイルをPowerShellで実行

settings.jsonにおける実際の設定作業

  1. VS Codeの設定画面を開く
    ショートカットキー操作 :Ctrl + ,(カンマ)
    画面操作 :ファイル(F)ユーザー設定設定 Ctrl + ,を選択

  2. 設定で検索欄でcode-runner executor mapと入力

  3. 設定項目 Code-runner: Executor Map にあるリンク「 settings.jsonで編集 」をクリック

    Code-runner: Executor Map の「 settings.jsonで編集 」による変化点 < クリックで折りたたみが開く >
    Code-runner
    {
        "python.linting.flake8Enabled": true,
        "editor.fontFamily": "'BIZ UDゴシック', Consolas, 'Courier New', monospace",
        "extensions.ignoreRecommendations": true,
        "[python]": {
            "editor.formatOnType": true
        },
        "code-runner.runInTerminal": true
    +    "code-runner.executorMap": {
    +        
    +        "javascript": "node",
    +        "java": "cd $dir && javac $fileName && java $fileNameWithoutExt",
    +        "c": "cd $dir && gcc $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
    +        "zig": "zig run",
    +        "cpp": "cd $dir && g++ $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
    +        "objective-c": "cd $dir && gcc -framework Cocoa $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
    +        "php": "php",
    +        "python": "python -u",
    +        "perl": "perl",
    +        "perl6": "perl6",
    +        "ruby": "ruby",
    +        "go": "go run",
    +        "lua": "lua",
    +        "groovy": "groovy",
    +        "powershell": "powershell -ExecutionPolicy ByPass -File",
    +        "bat": "cmd /c",
    +        "shellscript": "bash",
    +        "fsharp": "fsi",
    +        "csharp": "scriptcs",
    +        "vbscript": "cscript //Nologo",
    +        "typescript": "ts-node",
    +        "coffeescript": "coffee",
    +        "scala": "scala",
    +        "swift": "swift",
    +        "julia": "julia",
    +        "crystal": "crystal",
    +        "ocaml": "ocaml",
    +        "r": "Rscript",
    +        "applescript": "osascript",
    +        "clojure": "lein exec",
    +        "haxe": "haxe --cwd $dirWithoutTrailingSlash --run $fileNameWithoutExt",
    +        "rust": "cd $dir && rustc $fileName && $dir$fileNameWithoutExt",
    +        "racket": "racket",
    +        "scheme": "csi -script",
    +        "ahk": "autohotkey",
    +        "autoit": "autoit3",
    +        "dart": "dart",
    +        "pascal": "cd $dir && fpc $fileName && $dir$fileNameWithoutExt",
    +        "d": "cd $dir && dmd $fileName && $dir$fileNameWithoutExt",
    +        "haskell": "runghc",
    +        "nim": "nim compile --verbosity:0 --hints:off --run",
    +        "lisp": "sbcl --script",
    +        "kit": "kitc --run",
    +        "v": "v run",
    +        "sass": "sass --style expanded",
    +        "scss": "scss --style expanded",
    +        "less": "cd $dir && lessc $fileName $fileNameWithoutExt.css",
    +        "FortranFreeForm": "cd $dir && gfortran $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
    +        "fortran-modern": "cd $dir && gfortran $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
    +        "fortran_fixed-form": "cd $dir && gfortran $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
    +        "fortran": "cd $dir && gfortran $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
    +        "sml": "cd $dir && sml $fileName",
    +        "mojo": "mojo run"
    +    }
    }
    
  4. settings.jsonでpowershellの欄を個別に変更する

    UTF-8 BOMの変換処理を追加したsettings.json
    {
        // ~ 省略 ~ //
        
        "code-runner.runInTerminal": true,
        "code-runner.executorMap": {
    
            "javascript": "node",
            "java": "cd $dir && javac $fileName && java $fileNameWithoutExt",
            "c": "cd $dir && gcc $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
            "zig": "zig run",
            "cpp": "cd $dir && g++ $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
            "objective-c": "cd $dir && gcc -framework Cocoa $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
            "php": "php",
            "python": "python -u",
            "perl": "perl",
            "perl6": "perl6",
            "ruby": "ruby",
            "go": "go run",
            "lua": "lua",
            "groovy": "groovy",
    -         "powershell": "powershell -ExecutionPolicy ByPass -File",
            // Windows標準のPowerShell 5.1.x の場合
    +         "powershell": "cd $dir && Get-Content -Encoding utf8BOM .\\$fileName | Out-File -Encoding utf8BOM $Env:TEMP\\tempCodeRunnerFile_utf88BOM.ps1 && Remove-Item .\\$fileName -Force && powershell -ExecutionPolicy ByPass -File $Env:TEMP\\tempCodeRunnerFile_utf88BOM.ps1",
            // PowerShell Core の場合(私の場合、7.3.8)
    +         "powershell": "cd $dir && Get-Content -Encoding utf8BOM .\\$fileName | Out-File -Encoding utf8BOM $Env:TEMP\\tempCodeRunnerFile_utf88BOM.ps1 && Remove-Item .\\$fileName -Force && pwsh -ExecutionPolicy ByPass -File $Env:TEMP\\tempCodeRunnerFile_utf88BOM.ps1",
            "bat": "cmd /c",
            "shellscript": "bash",
            "fsharp": "fsi",
            "csharp": "scriptcs",
            "vbscript": "cscript //Nologo",
            "typescript": "ts-node",
            "coffeescript": "coffee",
            "scala": "scala",
            "swift": "swift",
            "julia": "julia",
            "crystal": "crystal",
            "ocaml": "ocaml",
            "r": "Rscript",
            "applescript": "osascript",
            "clojure": "lein exec",
            "haxe": "haxe --cwd $dirWithoutTrailingSlash --run $fileNameWithoutExt",
            "rust": "cd $dir && rustc $fileName && $dir$fileNameWithoutExt",
            "racket": "racket",
            "scheme": "csi -script",
            "ahk": "autohotkey",
            "autoit": "autoit3",
            "dart": "dart",
            "pascal": "cd $dir && fpc $fileName && $dir$fileNameWithoutExt",
            "d": "cd $dir && dmd $fileName && $dir$fileNameWithoutExt",
            "haskell": "runghc",
            "nim": "nim compile --verbosity:0 --hints:off --run",
            "lisp": "sbcl --script",
            "kit": "kitc --run",
            "v": "v run",
            "sass": "sass --style expanded",
            "scss": "scss --style expanded",
            "less": "cd $dir && lessc $fileName $fileNameWithoutExt.css",
            "FortranFreeForm": "cd $dir && gfortran $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
            "fortran-modern": "cd $dir && gfortran $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
            "fortran_fixed-form": "cd $dir && gfortran $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
            "fortran": "cd $dir && gfortran $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
            "sml": "cd $dir && sml $fileName",
            "mojo": "mojo run"
        }
    
        // ~ 省略 ~ //
    }
    

設定変更後に範囲選択で実行した結果

出力先をターミナルに設定 」と 「 Code RunnerでPowerShellの個別設定を追加
「出力先をターミナルに設定」と「Code RunnerでPowerShellの個別設定を追加」によりCode Runnerの個別実行が正常動作してWindowsフォームが表示された
画像:Code Runnerの個別実行が正常動作してWindowsフォームが表示

参考情報

まとめ

  • Code Runner を導入する事で選択範囲のコードのみ実行する事が可能
  • Code Runner では拡張子(言語)毎に実行コマンドを変更する事が可能
  • 今回検証したPowerShellスクリプトでは日本語が含まれていた事によりPowerShellで自動的にShift-JISとして処理され文字化けとエラーが発生していた
  • Code Runner で実行時に生成される一時ファイルをUTF-8 BOMに変換する事で正常動作した

関連記事

https://haretokidoki-blog.com/pasocon_powershell-startup/
https://zenn.dev/haretokidoki/articles/7e6924ff0cc960

GitHubで編集を提案

Discussion