0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【C#】Process.Startで「操作がキャンセルされました」エラーが出る原因はZone.Identifierだった

0
Posted at

はじめに

C#(Visual Studio 2017)で開発中、外部の実行ファイルを Process.Start で呼び出そうとしたところ、少し不可解なエラーに遭遇しました。
他のチームメンバーの環境では一切発生せず、自分の環境だけで起きた現象だったため、原因特定に少し手間取りました。

同じようなエラーでハマる方の参考になればと思い、備忘録としてまとめます。

開発環境と発生した現象

  • 環境: Visual Studio 2017 (C#) / (※VS2022も共存している環境)
  • やりたかったこと: Process.Start("test.exe", 引数) を実行して外部プログラムを呼び出す。
    ※test.exeは仮名です。

上記の処理を実行したところ、Win32Exception がスローされ、「操作はユーザーによって取り消されました」といった旨のダイアログが表示され、処理が落ちてしまいました。

※以下ダイアログイメージ
取り消されました_ERRORCODE.png

試したこと・疑ったこと(ハズレだったもの)

他の人の環境では動いていたため、最初は自分のPC環境のせいだと完全に疑っていました。

  1. 対象の.exe単体での実行
    プログラムからではなく、エクスプローラーから直接対象のtest.exeをダブルクリックで実行すると、何の問題もなく正常に動作しました。
  2. 管理者権限での実行
    test.exeを「管理者として実行」してみましたが、結果は変わらずエラーになりました。
  3. VS2022と2017の共存環境のせい?
    自分のPCにはVS2022とVS2017が両方インストールされていたため、環境の競合を疑いました。最終的にうまくいかなかった場合は、2022を削除して試そうとしましたが実際には行いませんでした。

原因究明:NativeErrorCodeからの特定

例外だけだと、「ユーザーが操作をキャンセルした」というふわっとした情報しか得られませんでした(自分では何もキャンセルしていないのに…)。

そこで、例外オブジェクトから NativeErrorCode を詳細に出力するようにコードを修正して探ったところ、どうやらWindowsのOS側のセキュリティ機構(SmartScreenなど)に引っかかり、実行が弾かれている挙動であることが見えてきました。

※以下ダイアログイメージ
取り消されました.png

真の原因:OneDriveとZone.Identifier(Mark of the Web)

結論から言うと、対象の実行ファイル一式に Zone.Identifier(代替データストリーム) が付与されていたことが原因でした。

今回は、実行ファイル一式の受け渡しを OneDrive経由 で行っていました。
インターネットやクラウドストレージ経由でダウンロードしたファイルには、Windowsの仕様で「インターネットから取得したファイルである」という目印(Zone.Identifier)が付与されます。

エクスプローラーから直接手動で実行した場合は問題なくとも、C#の Process.Start 経由で裏側から呼び出そうとすると、このセキュリティブロックが働き、「(システムによって)操作がキャンセルされた」としてエラーが返ってきていたようです。

※以下のようなダイアログが初回起動すると出るようです
ZONEIDETIFIERダイアログ.png

回避策:コード側で UseShellExecute を false にする

根本的な解決策(ファイルのブロック解除)の前に、実はC#のコード側を1行修正するだけでもこのエラーを回避することができます。

ProcessStartInfo クラスを使用して、UseShellExecute = false を明示的に指定して起動する方法です。

Process process = new Process();
process.StartInfo.FileName = "test.exe";
process.StartInfo.Arguments = "引数";
// シェル機能を使用せずに直接プロセスを起動する
process.StartInfo.UseShellExecute = false;

process.Start();

UseShellExecute = true (VS2017の直指定のデフォルト):OSのシェル(エクスプローラー)を介して実行されるため、Windowsのセキュリティ機構が Zone.Identifierを検知し、ブロック(操作のキャンセル)を発動させます。

UseShellExecute = false:シェルを介さず、OSのAPI(CreateProcess)を直接呼び出して実行します。UIを伴うセキュリティチェックがバイパスされるため、マークが付いたままでも起動できてしまいます。

手っ取り早くコード側だけで解決したい場合はこの方法でも動きます。ただ、セキュリティの観点や、「エクスプローラーから手動で実行したときの挙動と揃える」という意味では、やはりファイル自体の Zone.Identifier を削除しておくのが一番筋の良い対応と言えます。

ただし同僚が何も修正しないでエラーが出てない以上この対策を行うのは悪手でしたので採用しませんでした。

解決策:PowerShellで確認してブロックを解除する

プロパティ画面からもGUIで「ブロックの解除」を行えますが、PowerShellを使って「確認」と「解除」を行うのが確実です。

1. Zone.Identifierが付与されているか確認する

まずは、エラーの原因となっているファイルに本当に Zone.Identifier(代替データストリーム)が付いているかを確認します。PowerShellで以下のコマンドを実行します。

# 指定したファイルの代替データストリームを確認する
Get-Item ".\test.exe" -Stream *

対象ファイルにマークが付いている場合、ストリーム情報が出力されます。(付いていない場合はエラーになります)

# Zone.Identifierがついている場合
PSPath        : Microsoft.PowerShell.Core\FileSystem::C:\...\test.exe::$DATA
PSParentPath  : Microsoft.PowerShell.Core\FileSystem::C:\...
PSChildName   : test.exe::$DATA
PSDrive       : C
PSProvider    : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
FileName      : C:\...\test.exe
Stream        : :$DATA
Length        : 151552

PSPath        : Microsoft.PowerShell.Core\FileSystem::C:\...\test.exe:Zone.Identifier
PSParentPath  : Microsoft.PowerShell.Core\FileSystem::C:\...
PSChildName   : test.exe:Zone.Identifier
PSDrive       : C
PSProvider    : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
FileName      : C:\...\test.exe
Stream        : Zone.Identifier
Length        : 26

  1. ブロックを解除する(Unblock-File)
    付与されていることが確認できたら、Unblock-File コマンドレットを使用してブロックを解除します。
# 単体ファイルのブロックを解除する場合
Unblock-File -Path ".\test.exe"

# (参考)フォルダ内の全ファイルから一括で解除したい場合
Get-ChildItem -Path "対象フォルダのパス" -Recurse | Unblock-File

この手順で Zone.Identifier を削除したところ、C#の Process.Start から実行しても「操作がキャンセルされました」というエラーが出なくなり、無事に起動するようになりました!

おまけ:手元でエラーを意図的に再現(実験)する方法

この現象をテストしたい場合、PowerShellを使って意図的にローカルのファイルへ Zone.Identifier を付与することで再現可能です。

適当なexeファイル(test.exe)を用意し、PowerShellで以下を実行します。

# test.exe にインターネットゾーン (ZoneId=3) のマークを付ける
Set-Content -Path ".\test.exe" -Stream "Zone.Identifier" -Value "[ZoneTransfer]`r`nZoneId=3"

この状態でC#から Process.Start("test.exe") を実行するとエラーが再現します。その後、前述の Unblock-File で解除すると正常に起動するようになるので、挙動の確認に便利です。

まとめ

外部ファイルをProcess.Startで起動する際、Win32Exceptionが出たらセキュリティブロックを疑い、NativeErrorCodeを確認する。

OneDriveなどのクラウドストレージ経由で受け渡した.exeファイルには Zone.Identifierが付いている。

解除はPowerShellのGet-Item-Streamで確認し、Unblock-Fileで消すのがスマート。

エラーコードの字面だけ追うと見当違いの場所を探してしまいがちですが、ファイルの受け渡し経路にも罠が潜んでいました。皆さんもOneDrive等を経由した実行ファイルの扱いには気をつけましょう!

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?