はじめに
最近の (?) 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 を削除するのは PowerShell から可能で、なんならコマンドプロンプトで動作する専用のツールもありました。
これらを紹介するだけだと面白くないのと、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.');
FileName := '';
Exit(3);
end;
// Zone.Identifier が存在しなかったらエラー
FileName := FileName + ':' + ZI_FILENAME;
if not TFile.Exists(FileName) then
begin
Writeln(ZI_FILENAME + ' not found.');
FileName := '';
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 さんがやってそうだよなぁ?」と不安になって来た訳ですよ...
ヤッパリカー!!
ソースコード (その2)
こちらはフォルダにコピーして実行すると、そのフォルダおよびサブフォルダにあるファイルの Zone.Identifier を単純に削除するものです。
program RemoveZoneID;
{$APPTYPE CONSOLE}
uses
System.SysUtils, System.IOUtils;
const
ZI_FILENAME = 'Zone.Identifier';
begin
var Dir := TPath.GetDirectoryName(ParamStr(0));
for var FileName in TDirectory.GetFiles(Dir, '*.*', TSearchOption.soAllDirectories) do
begin
var FSFileName := FileName + ':' + ZI_FILENAME;
if TFile.Exists(FSFileName) then
DeleteFile(FSFileName);
end;
end.
対象となるフォルダにコピーしてダブルクリックで実行してください。
LAN 内 PC からコピーしたファイルのブロック
LAN 内 の共有フォルダからコピーしたファイルもブロックされる事があります。
\\file-server\share のような NetBIOS 名 (コンピュータ名) ではなく、\\192.168.1.100\share のような IP アドレスを含む UNC パスで共有されていると「信頼されていない共有」とみなされブロックされる (されやすい) ようです。
共有フォルダのファイルシステムが NTFS ではなくても、コピー時に Zone.Identifier が付加されます。
こういうのはイチイチ Zone.Identifier を削除するより「イントラネットだよ!」と設定しておくのが楽だと思います。
〔Win〕+〔R〕 で inetcpl.cpl と入力します。
[インターネットのプロパティ] が開くので、 [セキュリティ] タブで [ローカルインターネット] ゾーンを選択し、[サイト] ボタンを押します。
[詳細設定] ボタンを押します。
共有フォルダの UNC パスを入力して [追加] ボタンを押します。
共有フォルダがローカルインターネットゾーンに追加されました。
Explorer で共有フォルダからファイルコピーし、コピーされたファイルのプロパティを確認してみてください。
ローカルイントラネットへの追加で UNC パスではなく NetBIOS 名あるいは IP アドレスのみを指定すると、すべてのプロトコルがローカルイントラネットゾーンとして扱われます。
See also:
- FQDN または IP アドレスを使用すると、イントラネット サイトがインターネット サイトとして識別される (learn.microsoft.com)
- Windowsでダウンロードしたアプリがブロックされずに実行できるのはなぜ? (@IT)
イントラネットゾーンをレジストリで指定する
[インターネットオプション] でゾーンを設定した後、〔Win〕+〔R〕 で regedit と入力し、[レジストリエディタ] を起動します。
Windows 11 なら HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Ranges を開きます。
レジストリをエクスポートすると次のようになっていると思います。
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Ranges]
@=""
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Ranges\Range1]
":Range"="192.168.1.100"
"*"=dword:00000001
Range1 を真似て Range2, Range3 とコピーで増やしていくと設定用のレジストリファイルが簡単に作れます。
| 値 | 値の種類 | 説明 |
|---|---|---|
| :Range | REG_SZ | サイト。IP アドレスは範囲指定が可能。 |
| * | REG_DWORD | 値はプロトコルを示す (* はすべてのプロトコル)。1 を指定。 |
グループポリシーエディタでの設定 (非推奨)
〔Win〕+〔R〕 で gpedit.msc と入力します。
左側のツリーで [ユーザー構成 > 管理用テンプレート > Windows コンポーネント > 添付ファイル マネージャー] と辿り、ゾーン情報を添付ファイルに保存しない を有効に設定します。
グループポリシーエディタで設定する方法は、Zone.Identifier を一切付加しないというものであり、「LAN 内のファイルコピーに限って Zone.Identifier を付けない」といった制御はできません。セキュリティ上よろしくないので非推奨です。
おわりに
身も蓋もない事を言うと、最初から curl でダウンロードすりゃいいんですよ。
現在の所、curl で DL するとゾーン情報が付加されません。
-
実は NTFS ファイルストリームを読み書きする機能があります。 ↩



















