在 IIS 環境下使用 C# 操作 LibreOffice 進行檔案轉換

問題背景

在 Web 應用程式中,我們經常需要提供檔案格式轉換的功能。本文將詳細說明如何在 IIS 環境下,使用 C# 程式碼呼叫 LibreOffice 進行檔案格式轉換(例如:Excel 轉 ODS),並完整解決執行過程中可能遇到的權限問題。

環境需求

  1. Windows Server 環境
  2. IIS 網頁伺服器
  3. LibreOffice 套件
  4. .NET Framework 開發環境

可能遇到的問題

  1. 使用一般使用者帳號無法執行轉換
  2. IIS ApplicationPool 權限不足
  3. 檔案轉換過程沒有回應
  4. 程式執行但找不到輸出檔案
  5. 權限不足導致程式無法執行

完整解決方案

1. IIS 應用程式集區設定

首先確認 IIS 應用程式集區的設定:

  1. 開啟 IIS 管理員
  2. 找到您的應用程式集區(例如:DefaultAppPool)
  3. 右鍵 → 進階設定
  4. 設定以下項目:
    • 載入使用者設定檔 = True
    • 識別 = ApplicationPoolIdentity

2. Windows 使用者權限指派

設定執行檔案轉換的使用者權限:

  1. 開啟「本機安全性原則」

    • 按 Windows + R
    • 輸入 secpol.msc
    • 按確定
  2. 在左側樹狀目錄中依序展開

    • 安全性設定
    • 本機原則
    • 使用者權限指派
  3. 設定下列權限(對於您要使用的帳號,例如 IIS APPPOOL\DefaultAppPool 或特定使用者帳號)

    • 找到「以批次工作登入」(Log on as a batch job)

      • 按右鍵 → 內容
      • 點選「新增使用者或群組」
      • 加入使用者帳號
    • 找到「以服務方式登入」(Log on as a service)

      • 按右鍵 → 內容
      • 點選「新增使用者或群組」
      • 加入使用者帳號
    • 找到「允許本機登入」(Allow log on locally)

      • 按右鍵 → 內容
      • 點選「新增使用者或群組」
      • 加入使用者帳號
  4. 如果使用 IIS APPPOOL\DefaultAppPool

    • 上述三個權限都要加入 IIS APPPOOL\DefaultAppPool
    • 特別注意「以批次工作登入」這個權限一定要加入

3. 資料夾權限設定

為執行帳號設定適當的資料夾權限:

  1. 在以下資料夾按右鍵 → 內容 → 安全性 → 編輯
  2. 點選「新增」,輸入您使用的帳號(例如:IIS APPPOOL\DefaultAppPool)
  3. 給予以下權限:
    • 讀取權限
    • 寫入權限
    • 執行權限

需要設定的資料夾:

  • LibreOffice 安裝目錄(通常是 C:\Program Files\LibreOffice)
  • 轉換檔案的來源資料夾
  • 轉換檔案的目標資料夾
  • 暫存資料夾(例如:D:\temp)

4. 程式碼實作

以下是完整的程式碼實作,包含詳細的錯誤處理和記錄:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Security;
using System.IO;
using System.Configuration;

public class ImpersonateUser
{
// Windows API 常數定義
private const int LOGON32_LOGON_INTERACTIVE = 2;
private const int LOGON32_PROVIDER_DEFAULT = 0;
private const int LOGON32_LOGON_BATCH = 4;

[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out IntPtr phToken);

[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool ImpersonateLoggedOnUser(IntPtr hToken);

[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool RevertToSelf();

[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hObject);

public static void ConvertFile(string sourcePath, string destPath, string Format)
{
string sofficePath = @"C:\Program Files\LibreOffice\program\soffice.exe";
Process process = null;
IntPtr tokenHandle = IntPtr.Zero;

try
{
XLogFile.WriteLog($"原始執行身份: {System.Security.Principal.WindowsIdentity.GetCurrent().Name}");

// 取得使用者認證
string userName = ConfigurationManager.AppSettings["SystemAccount"].ToString();
string password = ConfigurationManager.AppSettings["SystemPassword"].ToString();
string domain = "."; // 本機帳號使用 "."

// 嘗試登入並取得權杖
bool loginSuccess = LogonUser(
userName,
domain,
password,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
out tokenHandle);

if (!loginSuccess)
{
int error = Marshal.GetLastWin32Error();
throw new Win32Exception(error, $"LogonUser 失敗. 錯誤碼: {error}");
}

// 模擬使用者
if (!ImpersonateLoggedOnUser(tokenHandle))
{
int error = Marshal.GetLastWin32Error();
throw new Win32Exception(error, "ImpersonateLoggedOnUser 失敗");
}

XLogFile.WriteLog($"切換後執行身份: {System.Security.Principal.WindowsIdentity.GetCurrent().Name}");

// 設定程序啟動資訊
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = sofficePath;
startInfo.Arguments = $"--headless --convert-to \"{Format}\" \"{sourcePath}\" --outdir \"{Path.GetDirectoryName(destPath)}\"";
startInfo.UseShellExecute = false;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
startInfo.CreateNoWindow = true;
startInfo.WorkingDirectory = Path.GetDirectoryName(sourcePath);

XLogFile.WriteLog($"完整命令: {startInfo.FileName} {startInfo.Arguments}");

// 執行程序
process = new Process();
process.StartInfo = startInfo;

// 處理輸出
process.OutputDataReceived += (sender, e) => {
if (!string.IsNullOrEmpty(e.Data))
XLogFile.WriteLog($"程序輸出: {e.Data}");
};
process.ErrorDataReceived += (sender, e) => {
if (!string.IsNullOrEmpty(e.Data))
XLogFile.WriteLog($"程序錯誤: {e.Data}");
};

process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();

XLogFile.WriteLog($"程序結束代碼: {process.ExitCode}");

// 檢查輸出檔案
string expectedOutputFile = Path.Combine(
Path.GetDirectoryName(destPath),
Path.GetFileNameWithoutExtension(sourcePath) + "." + Format);

if (File.Exists(expectedOutputFile))
{
XLogFile.WriteLog("檔案轉換成功");
}
else
{
XLogFile.WriteLog("警告:找不到輸出檔案");
}
}
catch (Exception ex)
{
XLogFile.WriteLog($"轉換過程發生錯誤: {ex.ToString()}");
throw;
}
finally
{
// 清理資源
if (process != null)
{
process.Close();
}

// 恢復原始身份
RevertToSelf();

// 關閉權杖
if (tokenHandle != IntPtr.Zero)
{
CloseHandle(tokenHandle);
}
}
}
}

5. Web.config 設定

在 Web.config 中加入使用者帳號設定:

1
2
3
4
5
6
<configuration>
<appSettings>
<add key="SystemAccount" value="您的使用者帳號" />
<add key="SystemPassword" value="您的密碼" />
</appSettings>
</configuration>

6. 使用方式

1
2
3
4
5
6
// 呼叫範例
ImpersonateUser.ConvertFile(
@"D:\Source\test.xlsx", // 來源檔案路徑
@"D:\Destination\test.ods", // 目標檔案路徑
"ods" // 轉換格式
);

完整檢查清單

設定完成後,使用以下清單檢查是否已完成所有必要設定:

  1. IIS 設定

    • 應用程式集區載入使用者設定檔設為 True
    • 應用程式集區識別設定正確
  2. 資料夾權限

    • LibreOffice 安裝目錄權限
    • 來源資料夾權限
    • 目標資料夾權限
    • 暫存資料夾權限
  3. 使用者權限指派

    • 以批次工作登入
    • 以服務方式登入
    • 允許本機登入
  4. Web.config 設定

    • SystemAccount 設定正確
    • SystemPassword 設定正確

常見問題排除

  1. 程式沒有回應

    • 檢查 IIS 應用程式集區的識別設定
    • 確認資料夾權限設定
    • 查看事件檢視器中的錯誤記錄
  2. 找不到輸出檔案

    • 確認目標資料夾的寫入權限
    • 檢查 LibreOffice 是否正確安裝
    • 查看記錄檔中的錯誤訊息
  3. 權限不足

    • 確認已設定正確的資料夾權限
    • 檢查使用者帳號是否有足夠權限
    • 確認 Web.config 中的帳號設定正確
  4. 設定完權限後仍無法執行

    • 重新啟動 IIS
    • 重新啟動應用程式集區
    • 必要時重新啟動伺服器

結論

要在 IIS 環境下成功使用 LibreOffice 進行檔案轉換,關鍵在於:

  1. 正確設定 IIS 應用程式集區
  2. 設定完整的 Windows 使用者權限
  3. 設定適當的資料夾權限
  4. 實作完整的錯誤處理機制
  5. 記錄詳細的執行過程

只要依照上述步驟仔細設定,就能順利完成檔案轉換的功能。如果遇到問題,也可以透過記錄檔快速找出問題所在。

希望這篇文章能幫助遇到類似問題的開發者,快速建立起可靠的檔案轉換機制!