Windows Container 안의 Microsoft Office 자동화

남정현
남정현
Jun 19 · 13 min read
Container Office in Hong Kong: https://commons.wikimedia.org/wiki/File:Container_office.jpg

주의 1: 이 글에서 설명하는 기능은 Microsoft가 공식적으로 보장하는 기능이 아니며, 어디까지나 개인적인 실험에 의하여 발견한 내용입니다. Microsoft 측의 소프트웨어 업데이트로 이 글에서 설명하는 기능이 갑자기 동작하지 않을 수 있습니다.

주의 2: 이 글에서 설명하는 컨테이너 기능을 이용하여 불특정 다수에게 서비스를 제공하는 것은 라이선스 위반이 될 수 있습니다. 어디까지나 기술적 가능성과 재미라는 관점에서만 이 글의 내용을 참고해주세요. (자세한 내용 보기)

Office 자동화에 대한 이야기

Microsoft Office는 Windows OS 만큼이나 긴 세월동안 많은 사람들에게 유용함을 인정받은 소프트웨어입니다. 그리고 이제는 전세계 어디서나 통용되고, 모든 사람들의 생활 속에 없어서는 안될 필수 소프트웨어의 반열에 굳게 자리잡았습니다.

Office는 사람들에게 보여주는 GUI 외에도, 작업을 좀 더 효율적이고 편리하게 처리할 수 있는 자동화에 관련된 기능도 풍부하게 갖추고 있습니다. 하지만 이런 자동화 기능은 서버 시나리오에는 걸맞지 않은 부분이 많았습니다. 성능 면에서나, 라이선스 면에서나 모두 그렇습니다.

여기에 대한 대안으로 OOXML 포맷이 공개되고 이를 기반으로 문서를 작성할 수 있도록 도와주는 여러 SDK와 3rd Party 라이브러리들이 많이 발전해왔습니다. 하지만 최신 버전의 Microsoft Office 문서를 손실 없이 완벽하게 다룰 수 있는 방법은 여전히 실제 Microsoft Office 제품을 설치한 컴퓨터를 이용하여 자동화하는 것이 최선입니다.

만약 이런 자동화를 Windows 컨테이너 안에서 처리할 수 있다면 유용할 것 같다는 생각을 하신 분들이 많을 것입니다. 하지만 Windows 컨테이너가 Office를 지원한다는 보장도 없고, 실제로 설치를 자동화하는 등의 작업을 하기 쉽지 않았습니다.

사전 조사

최근에 Windows Container의 새로운 이미지 유형인 Full Container 이미지가 Windows Server 2019를 기점으로 처음 출시되었고, Office를 구동하기 위해 필요한 요건들이 갖추어졌습니다. 덕분에 사용자와 상호작용할 수 있는 방법은 없지만, GUI 애플리케이션은 메시지 루프를 타고 정상적으로 실행이 가능하게 되었습니다.

여기에 최근 출시되는 Office는 Office Deployment Toolkit (이하 ODT)를 이용하여 설치를 커스터마이징하고 자동화할 수 있는 방법을 제공하고 있습니다. XML 문법을 사용하는 ODT 설정 파일을 만들어 실행한다면 어느정도는 실행이 될 수 있을 것이라 생각했습니다.

ODT 설정 파일 만들기

저는 다음과 같이 32비트 버전의 Office 2019를 자동으로 설치하도록 ODT 설정 파일을 config.xml 이라는 파일로 만들었습니다.

<Configuration>
<Add OfficeClientEdition="32" Channel="PerpetualVL2019">
<Product ID="ProPlus2019Volume">
<Language ID="ko-kr" />
</Product>
</Add>
<Display Level="None" AcceptEULA="TRUE" />
<Property Name="AUTOACTIVATE" Value="1"/>
</Configuration>

특별한 내용은 없지만, 32비트 버전의, Office 365 채널이 아닌 Office 2019 Perpetual License를 사용했다는 점이 중요합니다.

Dockerfile 만들기

ODT를 Microsoft 다운로드 센터 웹 사이트에서 받아서 실행하고 위의 config.xml 파일대로 Office 패키지를 내려받아 설치하는 Dockerfile의 초안을 만들어보았습니다.

FROM mcr.microsoft.com/windows:1903 AS buildWORKDIR C:\\odtsetup
ADD https://download.microsoft.com/download/2/7/A/27AF1BE6-DD20-4CB4-B154-EBAB8A7D4A7E/officedeploymenttool_11617-33601.exe odtsetup.exe
RUN odtsetup.exe /quiet /norestart /extract:C:\\odtsetup
FROM mcr.microsoft.com/windows:1903 AS downloadWORKDIR C:\\odtsetup
COPY --from=build C:\\odtsetup\\setup.exe .
ADD config.xml .
RUN setup.exe /download C:\\odtsetup\\config.xml
FROM mcr.microsoft.com/windows:1903
MAINTAINER rkttu
WORKDIR C:\\odtsetup
COPY --from=build C:\\odtsetup\\setup.exe .
COPY --from=download C:\\odtsetup\\Office .
ADD config.xml .
RUN setup.exe /configure C:\\odtsetup\\config.xml
WORKDIR /
RUN rmdir /s /q C:\\odtsetup
# https://stackoverflow.com/questions/10837437/interop-word-documents-open-is-null
RUN powershell -Command new-object -comobject word.application
RUN mkdir C:\\Windows\\SysWOW64\\config\\systemprofile\\Desktop
VOLUME C:\\data

사실 이 단계까지 여러 시행 착오를 거쳤습니다. Office 365 채널을 이용하여 Office를 설치한 경우, 64비트 버전을 사용하는 경우에는 의도한 것과 달리 정상적으로 작동하지 않거나 0이 아닌 오류 코드가 반환되면서 Docker 빌드가 중단되는 일을 여러번 겪었습니다.

마지막의 두 줄은 컨테이너 환경에서 정상적으로 애플리케이션이 실행될 수 있도록 하기 위해 필요한 코드입니다.

  • UI가 제공되지 않기 때문에 정확한 상황은 알 수 없지만, Word.Application COM Object를 만들어서 애플리케이션이 자동화를 처리할 수 있는 상태로 준비해야 이후 단계가 제대로 동작합니다.
  • 파일을 열어서 COM 객체를 반환하려고 할 때 사용자 프로필 디렉터리 기준으로 Desktop 디렉터리가 없으면 NULL 참조를 반환합니다. 이에 관한 Stackoverflow 링크가 있어 아래에 첨부합니다.

서두에도 말씀드렸듯이, 나중에 Microsoft가 Office에 대해 새로운 변화를 주게 되면 이 글에서 이야기한 내용은 유효하지 않게 될 수 있습니다.

이제 컨테이너를 빌드하기 위하여 다음과 같이 명령어를 실행하겠습니다. 이 때 주의할 것은 Windows 컨테이너 모드로 Docker Desktop이 실행되고 있어야 한다는 점입니다.

docker build -t o365full:latest .

DOCX 파일을 PDF 파일로 변환하는 CLI 앱 만들기

설치가 정상적으로 진행되고 컨테이너 이미지가 만들어진 것을 확인한 후에 Office PIA 샘플 코드를 사용하여 DOCX 문서를 PDF 파일로 변환하는 툴을 C# 코드로 작성해보았습니다.

// Requires office.dll and word interop pia.dll from GACusing Microsoft.Office.Interop.Word;
using System;
using System.IO;
using System.Linq;
namespace OfficePIATest
{
internal static class Program
{
[STAThread]
public static void Main(string[] args)
{
var input = args.ElementAtOrDefault(0) ?? string.Empty;
var output = args.ElementAtOrDefault(1) ?? string.Empty;
if (!File.Exists(input))
{
Console.Error.WriteLine("Cannot open the file `{0}`.", input);
Environment.Exit(1);
return;
}
if (string.IsNullOrWhiteSpace(output))
{
Console.Error.WriteLine("Invalid output path.");
Environment.Exit(2);
return;
}
var outputDir = Path.GetDirectoryName(output);
if (!Directory.Exists(outputDir))
Directory.CreateDirectory(outputDir);
var wordApp = new Application();
Console.Out.WriteLine("word app initialized.");
Document wordDocument = null;
var t = new System.Threading.Tasks.Task(() => wordDocument = wordApp.Documents.Open(input));
t.Start();
t.Wait();
if (wordDocument == null)
{
Console.Error.WriteLine("Word document is null reference.");
Environment.Exit(3);
return;
}
else
Console.Out.WriteLine("Word document opened.");
wordDocument.ExportAsFixedFormat(output, WdExportFormat.wdExportFormatPDF);
Console.Out.WriteLine("Export performed.");
wordDocument.Close(WdSaveOptions.wdDoNotSaveChanges,
WdOriginalFormat.wdOriginalDocumentFormat,
false); //Close document
Console.Write("Closing document");
wordApp.Quit(); //Important: When you forget this Word keeps running in the background
Console.Out.WriteLine("Quitting word app.");
}
}
}

위의 코드를 빌드하기 위해서는 Office PIA DLL 파일이 필요합니다. Office를 설치하면 PIA 파일은 자동으로 %windir%\Assembly\GAC_MSIL 디렉터리에 설치되며, Office 어셈블리와 Microsoft.Office.Word 어셈블리가 필요하므로 2개 어셈블리를 프로젝트 참조에 추가해야 합니다.

그리고 코드를 빌드할 때 중요한 부분이 하나 있습니다. 반드시 대상 프로세서는 x86으로 설정하거나, Bit-preferness를 32비트 기본으로 설정하여 빌드하도록 합니다. 컨테이너에 설치된 Office가 32비트 빌드이기 때문에 이 부분을 반영하지 않으면 프로그램이 제대로 실행되지 않습니다.

위의 코드를 빌드하여 만든 EXE 파일과 샘플 문서를 추가하기 위하여 Dockerfile 끝에 아래 줄을 추가합니다. 현재 작업 중인 디렉터리 아래에 Sample 이라는 디렉터리에 필요한 파일들이 들어가있다고 가정하겠습니다.

저는 위의 코드를 이용하여 빌드한 파일의 이름을 OfficePIATest.exe 로, 그리고 샘플 문서는 demo.docx 라는 파일로 Sample 디렉터리 안에 저장했습니다.

ADD sample .

그리고 다시 한 번 이미지를 빌드합니다.

docker build -t o365full:latest .

테스트하기

이제까지 설명했던 코드의 전체 내용을 GIST에 게시하였습니다. 이 링크를 클릭하여 확인하실 수 있습니다.

아래 명령을 실행하여 PDF 파일을 호스트에서 열어볼 수 있도록 마운트한 채로 컨테이너를 실행합니다.

docker run -v %cd%\data:c:\data --rm -it --name=o365fulltest o365full:latest cmd

컨테이너 실행 환경 안에서 다음 명령을 실행해봅니다.

OfficePIATest.exe C:\demo.docx C:\data\test.pdf

그러면 아래 그림과 같이 PDF 파일이 만들어지게 됩니다.

Windows 컨테이너 안에서 Word를 이용하여 DOCX 파일을 PDF 파일로 변환한 예시

워드 문서를 PDF 파일로 변환할 수 있는 더 빠르고 효과적인 방법들은 훨씬 많습니다. 하지만 보신 것과 같이 Office PIA 어셈블리를 사용하는 .NET 애플리케이션을 컨테이너 안에서 실행할 수 있는 것은 기술적으로 좋은 출발점이 될 수 있습니다.

마무리

서두에서 언급한 것처럼 Office는 서버용 애플리케이션이 아니기 때문에 라이선스 상, 그리고 기술 상의 문제로 서버 자동화용으로는 투입하기에 적절하지 않습니다.

하지만 Windows 컨테이너를 이용하여 원하는 목표를 좀 더 쉽게 달성할 수 있는 것은 기술적으로 진일보한 부분이라고 생각하여 짧게나마 글로 공유해보고 싶었습니다.

이 글에 대해 보강하거나 추가로 피드백을 주실 것이 있다면 환영합니다.

남정현의 블로그

DevOps related blogs

남정현

Written by

남정현

Azure와 .NET 기술을 즐겨 사용하는 개발자입니다. 한국 Azure 사용자 페이스북 그룹에서 활동합니다.

남정현의 블로그

DevOps related blogs