在ASP.NET Core Web API中用LibreOffice便携版转Excel为PDF遇退出码-1错误
LibreOffice便携版在IIS Express下转换PDF报错(退出码-1)解决指南
问题背景
曾使用LibreOffice便携版将Office文档转换为PDF,目前无法复现该操作,在IIS Express开发环境中运行代码时,持续触发ERROR: LibreOffice returned a non-zero exit code错误,退出码为-1。LibreOffice的soffice.exe放置在C:\inetpub\logs\LibreOfficePortable目录下,不确定是否存在权限配置问题。
相关代码
using System; using System.Diagnostics; using System.IO; using System.Security.AccessControl; using System.Security.Principal; using System.Text; using System.Threading.Tasks; public static class MemoryStreamExtensions { // Fixed LibreOffice path for IIS private static readonly string LibreOfficeExePath = @"C:\inetpub\logs\LibreOfficePortable\soffice.exe"; /// <summary> /// Converts an Office file (Excel, Word, etc.) in a MemoryStream to PDF using LibreOffice headless. /// </summary> /// <param name="inputStream">MemoryStream of the source file</param> /// <param name="originalFileName">Original file name with extension (e.g. "file.xlsx")</param> /// <returns>MemoryStream of generated PDF</returns> public static async Task<MemoryStream> ConvertToPdfAsync( this MemoryStream inputStream, string originalFileName) { if (inputStream == null) throw new ArgumentNullException(nameof(inputStream)); if (string.IsNullOrWhiteSpace(originalFileName)) throw new ArgumentException("File name must be provided", nameof(originalFileName)); // Debug log buffer var log = new StringBuilder(); log.AppendLine("=== LibreOffice PDF Conversion Debug ==="); log.AppendLine($"Timestamp: {DateTime.UtcNow}"); log.AppendLine($"Original Filename: {originalFileName}"); log.AppendLine($"LibreOffice Path: {LibreOfficeExePath}"); // Validate permissions BEFORE anything else try { log.AppendLine("Running ValidateLibreOfficePermissions..."); ValidateLibreOfficePermissions(LibreOfficeExePath); log.AppendLine("Permission validation PASSED."); } catch (Exception ex) { log.AppendLine("Permission validation FAILED."); log.AppendLine(ex.ToString()); throw new Exception(log.ToString()); } // Create temp directories string timestamp = DateTime.UtcNow.Ticks.ToString(); string baseTemp = Path.Combine(Path.GetTempPath(), $"LO_Convert_{timestamp}"); string inputDir = Path.Combine(baseTemp, "input"); string outputDir = Path.Combine(baseTemp, "output"); string userProfile = Path.Combine(baseTemp, "profile"); Directory.CreateDirectory(inputDir); Directory.CreateDirectory(outputDir); Directory.CreateDirectory(userProfile); log.AppendLine($"Temp root: {baseTemp}"); string tempInputFile = Path.Combine(inputDir, originalFileName); string tempOutputFile = Path.Combine( outputDir, $"{Path.GetFileNameWithoutExtension(originalFileName)}.pdf" ); // Write input file log.AppendLine($"Saving input file to: {tempInputFile}"); using (var fs = new FileStream(tempInputFile, FileMode.Create, FileAccess.Write)) { inputStream.Position = 0; await inputStream.CopyToAsync(fs); } // Build LO arguments string args = $"-env:UserInstallation=file:///{userProfile.Replace("\\", "/")}" + $" --headless --convert-to pdf --outdir \"{outputDir}\" \"{tempInputFile}\""; log.AppendLine("LO Arguments:"); log.AppendLine(args); var startInfo = new ProcessStartInfo { FileName = LibreOfficeExePath, Arguments = args, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; string stdout = ""; string stderr = ""; int exitCode = -999; try { log.AppendLine("Starting LibreOffice process..."); using var process = new Process { StartInfo = startInfo }; try { if (!process.Start()) throw new Exception("Process.Start() returned false — process not launched."); } catch (Exception ex) { log.AppendLine("FAILED to start LibreOffice process."); log.AppendLine(ex.ToString()); throw new Exception(log.ToString()); } log.AppendLine("Process started OK."); var outTask = process.StandardOutput.ReadToEndAsync(); var errTask = process.StandardError.ReadToEndAsync(); await process.WaitForExitAsync(); stdout = await outTask; stderr = await errTask; exitCode = process.ExitCode; log.AppendLine("=== STDOUT ==="); log.AppendLine(stdout); log.AppendLine("=== STDERR ==="); log.AppendLine(stderr); log.AppendLine($"Exit Code: {exitCode}"); } catch (Exception ex) { log.AppendLine("EXCEPTION during conversion:"); log.AppendLine(ex.ToString()); throw new Exception(log.ToString()); } // Error handling if (exitCode != 0) { log.AppendLine("ERROR: LibreOffice returned a non-zero exit code."); throw new Exception(log.ToString()); } if (!File.Exists(tempOutputFile)) { log.AppendLine($"ERROR: Output PDF not found at: {tempOutputFile}"); throw new Exception(log.ToString()); } log.AppendLine("SUCCESS: PDF Generated."); log.AppendLine($"PDF Path: {tempOutputFile}"); byte[] pdfBytes = await File.ReadAllBytesAsync(tempOutputFile); return new MemoryStream(pdfBytes); } /// <summary> /// Converts a MemoryStream to a Base64 string. /// </summary> public static string ToBase64(this MemoryStream stream) { if (stream == null) throw new ArgumentNullException(nameof(stream)); stream.Position = 0; return Convert.ToBase64String(stream.ToArray()); } private static void TryDeleteFile(string path) { try { if (File.Exists(path)) File.Delete(path); } catch { // ignore cleanup errors } } private static void ValidateLibreOfficePermissions(string exePath) { var debug = new StringBuilder(); debug.AppendLine("=== LibreOffice Permission Check ==="); debug.AppendLine($"Checking executable: {exePath}"); // 1. Does the file exist? if (!File.Exists(exePath)) throw new FileNotFoundException("LibreOffice executable not found.", exePath); debug.AppendLine("OK: File exists."); // 2. Can current identity read the file? try { using var fs = File.Open(exePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); debug.AppendLine("OK: Read access allowed."); } catch (Exception ex) { debug.AppendLine("ERROR: Cannot read the executable."); debug.AppendLine(ex.ToString()); throw new Exception(debug.ToString(), ex); } // 3. Can the user execute? (Check NTFS ACL) try { var fileInfo = new FileInfo(exePath); var acl = fileInfo.GetAccessControl(); // <-- This works in .NET 6/7/8 var rules = acl.GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount)); var identity = WindowsIdentity.GetCurrent(); var principal = new WindowsPrincipal(identity); bool hasExecute = false; foreach (FileSystemAccessRule rule in rules) { if (principal.IsInRole(rule.IdentityReference.Value)) { if (rule.FileSystemRights.HasFlag(FileSystemRights.ExecuteFile) && rule.AccessControlType == AccessControlType.Allow) { hasExecute = true; } } } if (!hasExecute) { throw new Exception( $"User '{WindowsIdentity.GetCurrent().Name}' does NOT have Execute rights on: {exePath}" ); } } catch (Exception ex) { throw new Exception("Failed while checking file ACL permissions for LibreOffice.", ex); } // 4. Check if the file is blocked by Windows string zonePath = exePath + ":Zone.Identifier"; if (File.Exists(zonePath)) { debug.AppendLine("WARNING: Executable is BLOCKED by Windows (Zone.Identifier stream)."); debug.AppendLine($"Path: {zonePath}"); debug.AppendLine("Right-click > Properties > Unblock is required."); throw new Exception(debug.ToString()); } debug.AppendLine("OK: Executable is not zone-blocked."); // 5. Try a safe process start (test if process creation is allowed) try { var psi = new ProcessStartInfo { FileName = "cmd.exe", Arguments = "/c echo ProcessTest", CreateNoWindow = true, UseShellExecute = false, RedirectStandardOutput = true }; using var proc = Process.Start(psi); proc.WaitForExit(); debug.AppendLine("OK: Process creation is allowed."); } catch (Exception ex) { debug.AppendLine("ERROR: Cannot start ANY process under this identity."); debug.AppendLine(ex.ToString()); throw new Exception(debug.ToString(), ex); } // If all checks passed: debug.AppendLine("ALL PERMISSIONS OK."); } }
排查与解决步骤
1. 补全权限配置
IIS Express默认运行身份为当前开发用户或IIS_IUSRS组用户,需确保:
- 给
C:\inetpub\logs\LibreOfficePortable文件夹及所有子项,分配运行用户完全控制权限 - 系统临时目录(默认
C:\Users\<用户名>\AppData\Local\Temp)也需给该用户写入权限,代码会在此创建临时工作目录
2. 修正用户配置文件URI格式
退出码-1常因UserInstallation路径格式错误导致,替换代码中参数构建逻辑:
// 用Uri类自动生成正确的file://格式路径,避免手动替换错误 string userProfileUri = new Uri(userProfile).AbsoluteUri; string args = $"-env:UserInstallation={userProfileUri}" + $" --headless --convert-to pdf --outdir \"{outputDir}\" \"{tempInputFile}\"";
3. 清理残留LibreOffice进程
Headless模式下LibreOffice可能残留后台进程,导致新进程启动失败,在启动前添加清理逻辑:
// 在启动LibreOffice前杀死所有残留的soffice进程 foreach (var process in Process.GetProcessesByName("soffice")) { try { process.Kill(); process.WaitForExit(); } catch { } }
4. 命令行直接测试
用IIS Express运行身份打开命令提示符,执行以下命令验证LibreOffice本身是否正常:
"C:\inetpub\logs\LibreOfficePortable\soffice.exe" --headless --convert-to pdf "C:\test\sample.docx" --outdir "C:\test\output"
- 若命令行报错:说明是LibreOffice配置或系统环境问题,与代码无关
- 若命令行成功:回到代码排查进程启动逻辑
5. 排除杀毒软件拦截
Windows Defender或第三方杀毒软件可能拦截IIS进程启动外部程序,暂时关闭实时保护测试,若恢复正常则将LibreOffice文件夹加入白名单。
内容的提问来源于stack exchange,提问作者Qiuzman
相关产品推荐
相关产品推荐

