OneDrive の共有リンク(1drv.ms)からフォルダーやファイルを C# の WebClient.DownloadFile() で .zip として保存したのに、Windows で開くと「圧縮(zip)フォルダーが無効」になることがあります。多くの場合、Zip が壊れているのではなく、リンク先が Zip そのものではなくHTML(プレビュー画面など)を返しているのが原因です。この記事では、原因の切り分け、正しい download 直リンクの作り方、実運用で事故らない検証コードまでまとめます。
起きている現象(よくある症状)
まずは状況を整理します。次のようなパターンに当てはまる場合、本記事の原因に該当する可能性が高いです。
- OneDrive の共有リンク(短縮 URL の 1drv.ms)を WebClient.DownloadFile() に渡して保存できる
- 保存されたファイル名は Download.zip などで拡張子も zip
- しかし開くと Windows が「圧縮(zip)フォルダーが無効」と表示する
- サイズが数 KB~数百 KB 程度と妙に小さい(フォルダーや大きいファイルのはずなのに)
- ブラウザで同じ URL を開くと、ダウンロードではなく OneDrive のプレビュー画面や共有ページが表示される
| 見えている症状 | 起きていること(典型) | 最短の確認方法 |
|---|---|---|
| 保存できるのに Zip が開けない | 中身が Zip ではなく HTML やエラーページ | メモ帳で開いて先頭が「<!DOCTYPE html>」等になっていないか見る |
| Zip のサイズが異常に小さい | 実体データではなく「案内ページ」を保存している | ファイルサイズと想定サイズを比較 |
| フォルダーを Zip にしたいのに失敗する | ブラウザでは JavaScript 等で生成・遷移するが、WebClient はそれを実行できない | ブラウザ開発者ツールで実際のダウンロード URL を確認 |
「圧縮(zip)フォルダーが無効」になる本当の原因
Windows の Zip(エクスプローラーの標準機能)は、拡張子が .zipでも中身が Zip 形式でないと開けません。Zip 形式のファイルは先頭付近にZip のシグネチャ(PK で始まる)を持つのが一般的です。
ところが、1drv.ms の共有リンクは「直接ダウンロード用の URL」ではありません。状況により、次のようなものが返ってきます。
- OneDrive のプレビュー用 HTML ページ
- 「ダウンロード準備中」などの案内 HTML(ブラウザは JS で次の URL に遷移するが、WebClient は遷移しない)
- 権限不足・期限切れ・ダウンロード禁止設定などのエラー HTML
- ログインを要求するサインイン画面 HTML
その結果、C# 側で .zip として保存できても、実体は Zip ではないため Windows が「圧縮(zip)フォルダーが無効」と判断します。
ポイント:「Zip が壊れた」ではなく、Zip ではない別コンテンツ(HTML 等)を Zip として保存してしまっているのが典型です。
まずやるべき切り分け:本当に Zip を受け取れているか確認する
対策に入る前に、保存されたファイルが Zip かどうかを機械的に判定できるようにしておくと、運用でハマりにくくなります。
保存後に「先頭バイト」で Zip か判定する
Zip は多くの場合、先頭 2 バイトが 0x50 0x4B(’P’ ‘K’)です。これをチェックし、Zip でなければ「HTML を保存している」可能性が極めて高いと判断できます。
using System;
using System.IO;
public static class ZipSniffer
{
public static bool LooksLikeZip(string path)
{
if (!File.Exists(path)) return false;
using (var fs = File.OpenRead(path))
{
if (fs.Length < 4) return false;
int b0 = fs.ReadByte();
int b1 = fs.ReadByte();
// 'P' 'K'
return b0 == 0x50 && b1 == 0x4B;
}
}
public static string ReadHeadAsText(string path, int maxBytes = 512)
{
byte[] buf = new byte[maxBytes];
using (var fs = File.OpenRead(path))
{
int read = fs.Read(buf, 0, buf.Length);
return System.Text.Encoding.UTF8.GetString(buf, 0, read);
}
}
}
Zip でないと判定された場合は、先頭 512 バイトをテキストとして表示してみると、原因が一発で分かることがあります(HTML、権限エラー、サインイン誘導など)。
トラブル時に見るべき項目(チェック表)
| チェック項目 | 正常(Zip)に近い目安 | 異常(HTML 等)の典型 |
|---|---|---|
| ファイルサイズ | フォルダー/ファイルの規模に見合う | 数 KB~数百 KB で頭打ち |
| 先頭バイト | PK…(Zip シグネチャ) | <!DOCTYPE html> / <html> / JSON エラーなど |
| Content-Type(参考) | application/zip / application/octet-stream 等 | text/html / text/plain |
| ブラウザでの挙動 | 即ダウンロード開始 | プレビュー画面、共有ページ、ログイン画面 |
根本対策:OneDrive の「download 直リンク」を使う
結論として、1drv.ms の共有リンクをそのまま DownloadFile に渡すのは、安定した方法ではありません。OneDrive の共有リンクは「閲覧ページ」になりやすく、WebClient が欲しい「ファイル本体(Zip)」を常に返す保証がないためです。
対処は、OneDrive の “download” 直リンクを使うことです。質問文にある手順は、まさにこの「直リンク化」に該当します。
手順:Embed から download URL を作る(OneDrive 個人向けでよく使う)
- OneDrive(Web)で対象(フォルダー/ファイル)を右クリック
- 埋め込み(Embed) を開く
- 表示される iframe の src を取り出す
- URL 内の embed を download に置換する
- https://onedrive.live.com/download?cid=…&resid=…&authkey=…&em=2 のような形式の URL を得る
- その URL を C# のダウンロード処理に渡す
重要:Embed から得られる URL は、共有設定やリンクの種類によっては取得できない場合があります。また OneDrive for Business(SharePoint ベース)では UI が異なり、Embed が出ないこともあります。その場合は後述の代替策(download=1、Graph、開発者ツールで実 URL 取得)を検討してください。
WebClient での最小実装(質問のコードの形)
download 直リンクに差し替えたうえでダウンロードすれば、少なくとも「HTML を Zip として保存してしまう」事故は大幅に減ります。
using System.Net;
string downloadUrl = "https://onedrive.live.com/download?cid=...&resid=...&authkey=...&em=2";
string localPath = @"C:\Users\xxxxx\Pictures\Download.zip";
using (var client = new WebClient())
{
client.DownloadFile(downloadUrl, localPath);
}
ただし、実運用で「ダウンロードできたつもり」を防ぐなら、保存後の Zip 判定を必ず入れてください(ネットワーク断、権限エラー HTML、期限切れ HTML などを検知できます)。
using System;
using System.Net;
string downloadUrl = "https://onedrive.live.com/download?cid=...&resid=...&authkey=...&em=2";
string localPath = @"C:\temp\Download.zip";
using (var client = new WebClient())
{
// 企業環境などでプロキシが絡む場合は client.Proxy を明示するケースもあります
client.Headers.Add("User-Agent", "Mozilla/5.0"); // 互換性向上のため(必須ではありません)
client.DownloadFile(downloadUrl, localPath);
}
if (!ZipSniffer.LooksLikeZip(localPath))
{
string head = ZipSniffer.ReadHeadAsText(localPath);
throw new InvalidOperationException("Zip ではない内容を受信しました。先頭: " + head);
}
より堅牢にするなら HttpClient(ストリーミング+検証+一時ファイル)
WebClient は手軽ですが、細かい制御(タイムアウト、途中失敗時の扱い、ヘッダー、ストリーミング、検証など)をやり始めると限界が出ます。安定性を重視するなら HttpClient をおすすめします。
特に重要なのは次の 3 つです。
- ダウンロードは一時ファイルに保存し、完了後にリネームする(途中失敗のゴミを残さない)
- 先頭バイトや ZipArchive のオープンで検証してから成功扱いにする
- タイムアウトやリトライ方針を決める(巨大フォルダーの Zip 化で時間がかかることがある)
using System;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
public static class OneDriveDownloader
{
public static async Task DownloadZipAsync(string downloadUrl, string destinationPath, CancellationToken ct)
{
string tempPath = destinationPath + ".partial";
using (var handler = new HttpClientHandler
{
AllowAutoRedirect = true
})
using (var http = new HttpClient(handler))
{
http.Timeout = TimeSpan.FromMinutes(10);
http.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0");
using (var resp = await http.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead, ct))
{
resp.EnsureSuccessStatusCode();
await using (var inStream = await resp.Content.ReadAsStreamAsync(ct))
await using (var outStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None))
{
await inStream.CopyToAsync(outStream, 81920, ct);
}
}
}
// 受信内容が Zip か確認
if (!ZipSniffer.LooksLikeZip(tempPath))
{
string head = ZipSniffer.ReadHeadAsText(tempPath);
File.Delete(tempPath);
throw new InvalidOperationException("Zip ではない内容を受信しました。先頭: " + head);
}
// 成功確定:置き換え
if (File.Exists(destinationPath)) File.Delete(destinationPath);
File.Move(tempPath, destinationPath);
}
}
この形にしておくと、「zip ではない HTML を受け取った」「途中で切れた」「権限がなくてエラーページだった」などのケースを例外として扱えるようになります。
共有リンクの種類で変わる:URL パターン別の考え方
OneDrive といっても、個人向け(onedrive.live.com)と、法人向け(OneDrive for Business=SharePoint)でリンクの構造が異なります。混同するとハマるので、まずは自分が扱っているリンクがどれか確認してください。
| リンクの見た目 | 主な意味 | そのまま DownloadFile で安定する? | 推奨アプローチ |
|---|---|---|---|
| https://1drv.ms/… | 短縮共有リンク(プレビュー/共有ページに行きやすい) | 安定しない | download 直リンクに変換(Embed→download など) |
| https://onedrive.live.com/embed?… | 埋め込み用(iframe) | そのままでは目的が違う | embed → download に置換して直リンク化 |
| https://onedrive.live.com/download?… | 直接ダウンロード用 | 比較的安定 | これを使う(保存後に Zip 判定を推奨) |
| https://{tenant}-my.sharepoint.com/… | OneDrive for Business / SharePoint の共有リンク | ケースによる | download パラメータ付与、または Graph API |
OneDrive for Business(SharePoint)でよく使われる考え方
法人向けの共有リンクは、URL 末尾に download=1 を付けると「ダウンロード挙動」になりやすいケースがあります(リンクの種類や組織設定で異なる場合があります)。
例:
- 共有リンク:
https://contoso-my.sharepoint.com/:f:/g/personal/xxx/xxxxx?e=YYYYYY - ダウンロード寄りにする:
https://contoso-my.sharepoint.com/:f:/g/personal/xxx/xxxxx?e=YYYYYY&download=1
注意:組織の共有ポリシーで「ダウンロード禁止」になっている、認証が必須、条件付きアクセスがある、などの場合は、パラメータで解決しないことがあります。その場合は正攻法として、認証付きの API(Graph)で取得するか、管理者/共有者に設定変更を依頼してください。
フォルダーを Zip にするときに特にハマるポイント
ファイル単体よりも、フォルダーを「サーバー側で Zip 化してダウンロード」するケースの方が、HTML を拾いやすい傾向があります。理由はシンプルで、ブラウザでは次のような“人間向けの挙動”が混ざるからです。
- 「Zip を作っています…」のような中間ページ(HTML)を一度返す
- JavaScript で生成状況を確認し、完了したら本体 URL に遷移する
- 大容量だと分割・制限・タイムアウトが発生しやすい
WebClient は JavaScript を実行しないので、中間ページ(HTML)をそのまま .zip として保存してしまい、「圧縮(zip)フォルダーが無効」に直行します。
この場合の現実的な対策は次のいずれかです。
- OneDrive が返すdownload 直リンク(フォルダー用)を取得できるなら、それを使う
- うまく取れない/安定しないなら、Microsoft Graphでファイル一覧を取得し、ローカルで Zip を作る
「Zip が無効」以外も含めた、よくある落とし穴と対処
| 落とし穴 | 何が起きるか | 対処の方向性 |
|---|---|---|
| 共有リンクをそのまま使う(1drv.ms) | HTML(共有ページ/プレビュー)を保存して Zip が無効 | download 直リンクに変換する |
| ダウンロード禁止設定 | ダウンロードできたように見えて実体はエラーページ | 共有設定を見直す(権限/ポリシー) |
| リンク期限切れ | 短い HTML メッセージになりやすい | 新しいリンクを発行、エラー検知を入れる |
| 途中で通信が切れる | Zip が部分的にしか保存されず破損扱い | 一時ファイル+検証+リトライ |
| 巨大フォルダーの Zip 化 | 生成待ちの HTML を拾う、タイムアウト | Graph で分割取得してローカル Zip、またはタイムアウト設計 |
| プロキシ/SSL/TLS の影響 | 別ページへ誘導、または接続失敗 | プロキシ設定、TLS 設定、HttpClient で制御 |
実運用での安全策:ダウンロード後に Zip を「開けるか」まで検証する
「PK なら Zip っぽい」は強い判定ですが、より確実にするなら .NET の ZipArchive で実際に開けるか確認します。これにより、途中で切れたファイルも検出できます。
using System;
using System.IO;
using System.IO.Compression;
public static class ZipValidator
{
public static void ValidateZipOrThrow(string zipPath)
{
if (!File.Exists(zipPath))
throw new FileNotFoundException("ファイルが存在しません", zipPath);
// まずは軽いシグネチャ確認
if (!ZipSniffer.LooksLikeZip(zipPath))
throw new InvalidOperationException("Zip 形式ではない可能性があります(PK ではありません)。");
// 実際に開けるか確認
using (var fs = File.OpenRead(zipPath))
using (var archive = new ZipArchive(fs, ZipArchiveMode.Read, leaveOpen: false))
{
// エントリ一覧に触れて例外が出ないかを見る
_ = archive.Entries.Count;
}
}
}
検証まで入れておけば、ユーザーから「Zip が無効」と言われる前に、アプリ側で「ダウンロードが成立していない」ことを検知できます。ログに先頭数百バイト(HTML 断片など)を残すと、現場復旧が速くなります。
確実性重視の別解:Microsoft Graph で取得してローカルで Zip を生成する
「フォルダーを丸ごと Zip にしたい」「共有リンクの仕様変更や UI 依存を避けたい」「ダウンロード制御や監査を入れたい」といった要件があるなら、Microsoft Graphでフォルダー配下のファイル一覧を取得し、必要なファイルを個別にダウンロードしてローカルで Zip を生成する方法が堅牢です。
大まかな流れは以下です。
- Microsoft Entra ID(旧 Azure AD)でアプリ登録し、必要な権限を用意する
- OAuth 2.0 でアクセストークンを取得する(ユーザー委任/アプリ権限は要件次第)
- Graph API で対象フォルダーの children を列挙する
- 各ファイルをダウンロードしながら ZipArchive に追加する
実装の雰囲気(骨格だけ):
using System;
using System.IO;
using System.IO.Compression;
using System.Net.Http;
using System.Threading.Tasks;
public static class GraphZipBuilder
{
public static async Task BuildZipAsync(HttpClient graphHttp, string folderChildrenApiUrl, string outputZipPath)
{
// folderChildrenApiUrl の例(概念):
// [https://graph.microsoft.com/v1.0/me/drive/items/{item-id}/children](https://graph.microsoft.com/v1.0/me/drive/items/{item-id}/children)
// 1) children を取得(ここでは JSON のパース部分は省略)
// 2) file の downloadUrl(または content エンドポイント)を辿ってストリーミング
// 3) ZipArchive に追加
using var zipFs = new FileStream(outputZipPath, FileMode.Create, FileAccess.Write, FileShare.None);
using var zip = new ZipArchive(zipFs, ZipArchiveMode.Create);
// 例:1ファイル追加のイメージ
// var entry = zip.CreateEntry("path/in/zip/sample.jpg", CompressionLevel.Optimal);
// await using var entryStream = entry.Open();
// await using var fileStream = await graphHttp.GetStreamAsync(fileDownloadUrl);
// await fileStream.CopyToAsync(entryStream);
}
}
Graph の方式は初期設定が必要ですが、共有リンクの「ページ挙動」に左右されにくく、フォルダー内ファイルの取捨選択や命名規則、ログ、再試行、差分取得なども設計しやすいのがメリットです。
| 方式 | 手軽さ | 安定性 | フォルダー一括 | 運用設計(検証/監査/再試行) |
|---|---|---|---|---|
| 共有リンク(1drv.ms)を直接 DownloadFile | 高い | 低い | 不安定 | 弱い(HTML 誤保存が起きやすい) |
| download 直リンク(onedrive.live.com/download) | 中 | 中〜高 | 条件次第 | 中(保存後検証でかなり実用的) |
| Microsoft Graph + ローカル Zip 生成 | 低い(準備が必要) | 高い | 強い | 強い(要件に合わせて作り込める) |
現場で効くチェックリスト(再発防止)
- 「共有リンク」と「直接ダウンロード用リンク」を混同しない(1drv.ms は要注意)
- ダウンロード処理は必ず検証する(PK 判定+ZipArchive でのオープン)
- 保存は一時ファイル→成功確定後にリネームする
- 失敗時は先頭数百バイト(HTML 断片)をログに残す
- フォルダー一括を安定させたいなら Graph 方式も検討する
まとめ
OneDrive の共有リンク(1drv.ms)を C# の WebClient.DownloadFile() で .zip として保存して「圧縮(zip)フォルダーが無効」になるのは、Zip が壊れているというより、Zip ではない HTML などの別コンテンツを保存してしまっているケースがほとんどです。確実にするには、onedrive.live.com/download のようなdownload 直リンクを使い、さらに保存後に Zip かどうかを検証してから成功扱いにするのが実務的です。フォルダー配下を確実に固めたい場合は、Microsoft Graph で取得してローカルで Zip を生成する方式が強力な選択肢になります。

コメント