CAB (Cabinet) 是微軟常用的壓縮格式,適用於驅動程式套件、系統更新等場景。.NET Framework 未內建 CAB 操作 API,常見做法是呼叫系統內建的 makecab.exe 與 expand.exe 命令列工具。本文提供一個封裝良好的 Cab 類別,支援多檔案壓縮、解壓縮、臨時目錄管理與錯誤處理。
CabFileName(輸出/來源 .cab 路徑)、FileList(待壓縮或解壓出的檔案清單)%Templates%\CabTemp\)MakeCab() 壓縮、ExpandCab() 解壓縮using System;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
using System.Text;
namespace Com.ImYan.CabHelper
{
public class Cab
{
#region 屬性
private string _cabFileName;
public string CabFileName
{
get { return _cabFileName; }
set { _cabFileName = value; }
}
private List _fileList = new List();
public List FileList
{
get { return _fileList; }
set { _fileList = value; }
}
private string TempDir;
#endregion
#region 建構子
public Cab()
{
// 指定並建立臨時目錄
this.TempDir = Environment.GetFolderPath(Environment.SpecialFolder.Templates);
this.TempDir = string.Format("{0}\\CabTemp\\", this.TempDir);
if (Directory.Exists(this.TempDir)) Directory.Delete(this.TempDir, true);
Directory.CreateDirectory(this.TempDir);
}
#endregion
#region 私有方法
// 產生 makecab 所需的 list.txt
private string CreateListFile()
{
try
{
string listFilePath = Path.Combine(this.TempDir, "list.txt");
string listContent = string.Empty;
for (int i = 0; i < this.FileList.Count; i++)
{
listContent += string.Format("\"{0}\"\r\n", this.FileList[i]);
}
using (FileStream fs = new FileStream(listFilePath, FileMode.Create, FileAccess.Write))
using (StreamWriter sw = new StreamWriter(fs, Encoding.Default))
{
sw.Write(listContent);
}
return listFilePath;
}
catch (Exception ex)
{
return ex.Message;
}
}
// 執行命令列指令並回傳輸出
private string RunCommand(string cmdString)
{
Process p = new Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.CreateNoWindow = true;
p.Start();
p.StandardInput.WriteLine(cmdString);
p.StandardInput.WriteLine("exit");
p.WaitForExit(60000);
string output = p.StandardOutput.ReadToEnd();
p.Close();
return output;
}
// 從臨時目錄尋找 .cab 並移動到指定輸出路徑
private void MoveCabFile()
{
string cabFilePath = string.Empty;
List allFiles = GetFilesFromDir(this.TempDir);
foreach (string file in allFiles)
{
if (Path.GetExtension(file).ToLower() == ".cab")
{
cabFilePath = file;
break;
}
else
{
File.Delete(file); // 清理非 cab 檔
}
}
File.Move(cabFilePath, this.CabFileName);
}
// 遞迴取得目錄下所有檔案
private List GetFilesFromDir(string dir)
{
List files = new List(Directory.GetFiles(dir));
foreach (string childDir in Directory.GetDirectories(dir))
{
files.AddRange(GetFilesFromDir(childDir));
}
return files;
}
// 解析 expand 輸出,取得解壓出的檔案路徑
private void GetExpandedFileByCmdResult(string cmdResult)
{
this._fileList = new List();
string[] lines = cmdResult.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
string key = string.Format("將 {0} 展開成", this.CabFileName.ToLower());
foreach (string line in lines)
{
if (line.Trim().StartsWith(key))
{
string filePath = line.Replace(key, "").Trim().Replace("。", "");
if (File.Exists(filePath))
this._fileList.Add(filePath);
}
}
}
#endregion
#region 公有方法
// 壓縮建立 CAB
public bool MakeCab(out string errorInfo)
{
errorInfo = string.Empty;
try
{
string listFile = this.CreateListFile();
if (!File.Exists(listFile)) throw new Exception("生成 list.txt 失敗!");
string cmd = "makecab /F " + listFile;
string result = this.RunCommand(cmd);
this.MoveCabFile();
if (File.Exists(this.CabFileName)) return true;
errorInfo = string.Format("檔案 {0} 生成失敗!", this.CabFileName);
return false;
}
catch (Exception ex)
{
errorInfo = ex.Message;
return false;
}
}
// 解壓縮 CAB
public bool ExpandCab(out string errorInfo)
{
errorInfo = string.Empty;
try
{
if (!File.Exists(this.CabFileName)) throw new Exception("CAB 檔不存在!");
if (!Directory.Exists(this.TempDir)) Directory.CreateDirectory(this.TempDir);
string tempPath = this.TempDir.TrimEnd('\\');
string cmd = string.Format("expand \"{0}\" \"{1}\" -f:*", this.CabFileName, tempPath);
string result = this.RunCommand(cmd);
this.GetExpandedFileByCmdResult(cmdResult);
return true;
}
catch (Exception ex)
{
errorInfo = ex.Message;
return false;
}
}
#endregion
}
}
Cab cab = new Cab();
cab.CabFileName = @"D:\Output\MyFiles.cab";
cab.FileList.Add(@"D:\Source\File1.txt");
cab.FileList.Add(@"D:\Source\File2.doc");
string err;
if (cab.MakeCab(out err))
Console.WriteLine("壓縮成功!");
else
Console.WriteLine("失敗:" + err);
Cab cab = new Cab();
cab.CabFileName = @"D:\Output\MyFiles.cab";
string err;
if (cab.ExpandCab(out err))
{
Console.WriteLine("解壓縮成功,檔案清單:");
foreach (string f in cab.FileList) Console.WriteLine(f);
}
else
Console.WriteLine("失敗:" + err);
makecab.exe 與 expand.exe,需確保執行環境為 Windows。%Templates%\CabTemp\,多執行緒同時使用時可能衝突,建議改用 Path.GetTempPath() + 隨機子目錄。expand 輸出的關鍵字「將 {cab} 展開成」為中文系統預設訊息,英文系統需調整關鍵字。WaitForExit(60000) 可能不足,建議改為非同步讀取輸出流或增加逾時。透過封裝命令列工具,我們在不引入第三方函式庫的情況下,快速獲得 CAB 壓縮/解壓縮功能。若專案需跨平台或追求更高效能,可考慮使用 SharpCompress 或 Microsoft.Deployment.Compression.Cab (Dism API) 等純 managed 實作。