はじめに
最近の (?) Windows はインターネットから DL したファイルを開こうとすると警告が出てウザい事があります。
詳細は以下に譲るとして、
つまりはファイルにゾーン情報が付加されているために実行 (やオープン) がブロックされてしまうという事です。
インターネットから DL したファイルのブロック
NTFS のストリームについては以下が詳しいです。
例えば NONAME.TXT というファイルをインターネットから DL したら、Zone.Identifier というストリームが付与されるという事です。
この Zone.Identifier は NONAME.TXT:Zone.Identifier という感じでアクセスする事が可能で、コマンドプロンプトなら MORE コマンドが対応しています。
試しにやってみよう
以下のページから tead_229_full.exe を DL して実行してみます。中身はただのテキストエディタですのでご心配なく 1。
ファイルのプロパティを見るとセキュリティ情報が付いています。
more コマンドでも確認できました。ZoneID が 3 (インターネット) になっています。
実行すると見事にブロックされました。
Delphi で Zone.Identifier を削除する?
ググってみたら、NTFS ファイルストリームの Zone.Identifier を削除するのはコマンドプロンプトでも Power Shell でも可能で、なんなら専用のツールもありました。
- ファイル・ストレージ・マウント・ドライブに Zone.Identifier ファイルが作成される (Oracle ヘルプセンター)
- PowerShellメモ 一括でブロックの解除(ZoneId) (Qiita: @Kosen-amai)
- Streams v1.6 (learn.microsoft.com)
これらを紹介するだけだと面白くないのと、Zone.Identifier の ReferenceUrl とか HostURL とかの情報は後々残しておくと便利そうだと思ったので、ZoneID を 0 (マイコンピュータ) に設定して回避するツールを Delphi で作る事にしました。
ソースコード
コンソールアプリケーションとして作ってみました。Community Edition でもコンパイルできます。
program StripZoneID;
{$APPTYPE CONSOLE}
uses
System.Classes, System.SysUtils, System.IOUtils;
const
ZI_FILENAME = 'Zone.Identifier';
var
FileName: string;
function Check: Integer;
begin
// 正常終了にセット
Result := 0;
// パラメータなしで実行されたらエラー
if ParamCount = 0 then
Exit(1);
// ファイルのパラメータ位置を特定
var FileIdx := -1;
for var i:=1 to ParamCount do
begin
var s := ParamStr(i);
if not CharInSet(s[1], ['-', '/']) then
begin
FileIdx := i;
Break;
end;
end;
// ファイルが指定されていなかったらエラー
if FileIdx < 1 then
begin
Writeln('No file has been specified.');
Exit(2);
end;
// ファイルが存在しなかったらエラー
FileName := ParamStr(FileIdx);
if not TFile.Exists(FileName) then
begin
Writeln('File not found.');
Exit(3);
end;
// Zone.Identifier が存在しなかったらエラー
FileName := FileName + ':' + ZI_FILENAME;
if not TFile.Exists(FileName) then
begin
Writeln(ZI_FILENAME + ' not found.');
Exit(4);
end;
end; { Check }
begin
// エラーチェック
ExitCode := Check();
// ExitCode が 0 でなかったら USAGE を表示して終了
if ExitCode > 0 then
begin
var s := '''
USAGE: StripZoneID [[/d] [/v] [/z:<id>]] FileName
/d Delete Zone.Identifier
/v View Zone.Identifier
/z:<id> Set ZoneID (0..4)
''';
Writeln(s);
Exit;
end;
if FindCmdLineSwitch('v', True) then
begin
// /v スイッチが指定されていたら Zone.Identifier を表示
var SL := TStringList.Create;
try
SL.LoadFromFile(FileName);
Writeln(SL.Text);
finally
SL.Free;
end;
end
else if FindCmdLineSwitch('d', True) then
begin
// /d スイッチが指定されていたら Zone.Identifier を削除
DeleteFile(FileName);
end
else
begin
// /z スイッチが指定されていたらその値を ZoneID に設定
// /z スイッチが指定されていなかったら 0 を ZoneID に設定
var v: string;
var HasValue := FindCmdLineSwitch('z', v, True, [clstValueAppended]);
if not HasValue or (Length(v) <> 1) or not CharInSet(v[1], ['0'..'4']) then
v := '0';
var SL := TStringList.Create;
try
SL.LoadFromFile(FileName);
SL.Values['ZoneID'] := v;
SL.SaveToFile(FileName);
finally
SL.Free;
end;
end;
end.
コマンドラインスイッチは次の通りです:
- ファイルしか指定しなかったら、
Zone.IdentifierのZoneIDを 0 に設定 -
/vスイッチが指定されていたらZone.Identifierの中身を表示 -
/dスイッチが指定されていたらZone.Identifierを削除 -
/z:<id>(または/z<id>) スイッチが指定されていたら、Zone.IdentifierのZoneIDを id に設定 (id=0..4)
設定可能な ZoneID の値は次の通りです:
| ZoneId | 内容 |
|---|---|
| 0 | マイコンピュータ (ローカルマシン) |
| 1 | ローカルイントラネット |
| 2 | 信頼済みサイト |
| 3 | インターネット (保護ビューや警告の対象) |
| 4 | 制限付きサイト |
Delphi のルーチンやメソッドがすべて NTFS ファイルストリーム対応という訳ではないので、対応しているものを使っています。
実行
あら、便利!!
通常 (スイッチなし):
/Z (ZoneID 設定):
/D (Zone.Identifier 削除):
嫌な予感
さて、ここまで記事を書いた段階で「こういうの Mr.XRAY さんがやってそうだよなぁ?」と不安になって来た訳ですよ...
ヤッパリカー!!
おわりに
身も蓋もない事を言うと、最初から curl でダウンロードすりゃいいんですよ。
現在の所、curl で DL するとゾーン情報が付加されません。
-
実は NTFS ファイルストリームを読み書きする機能があります。 ↩








