OneDrive共有リンク(1drv.ms)をC#でZIPダウンロードすると「圧縮(zip)フォルダーが無効」になる原因と解決策

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 を生成する方式が強力な選択肢になります。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次