Windows サービスを介して Office InfoPath 2010 の複数の並列インスタンスを自動化しようとしています。サービスからの Office の自動化がサポートされていないことは理解していますが、これは顧客の要件です。
他の Office アプリケーションを並行して自動化できますが、InfoPath の動作は異なります。
私が見つけたのは、いくつの並列呼び出しが行われたとしても、作成される INFOPATH.EXE プロセスのインスタンスは 1 つしかないということCreateObject("InfoPath.Application")
です。これとは対照的に、WINWORD.EXE の複数のインスタンスは、同様のメカニズムを介して作成できます。CreateObject("Word.Application")
この問題を再現するには、単純なコンソール アプリケーションを使用できます。
static void Main(string[] args) {
// Create two instances of word in parallel
ThreadPool.QueueUserWorkItem(Word1);
ThreadPool.QueueUserWorkItem(Word2);
System.Threading.Thread.Sleep(5000);
// Attempt to create two instances of infopath in parallel
ThreadPool.QueueUserWorkItem(InfoPath1);
ThreadPool.QueueUserWorkItem(InfoPath2);
}
static void Word1(object context) {
OfficeInterop.WordTest word = new OfficeInterop.WordTest();
word.Test();
}
static void Word2(object context) {
OfficeInterop.WordTest word = new OfficeInterop.WordTest();
word.Test();
}
static void InfoPath1(object context) {
OfficeInterop.InfoPathTest infoPath = new OfficeInterop.InfoPathTest();
infoPath.Test();
}
static void InfoPath2(object context) {
OfficeInterop.InfoPathTest infoPath = new OfficeInterop.InfoPathTest();
infoPath.Test();
}
InfoPathTest クラスと WordTest クラス (VB) は別のプロジェクトにあります。
Public Class InfoPathTest
Public Sub Test()
Dim ip As Microsoft.Office.Interop.InfoPath.Application
ip = CreateObject("InfoPath.Application")
System.Threading.Thread.Sleep(5000)
ip.Quit(False)
End Sub
End Class
Public Class WordTest
Public Sub Test()
Dim app As Microsoft.Office.Interop.Word.Application
app = CreateObject("Word.Application")
System.Threading.Thread.Sleep(5000)
app.Quit(False)
End Sub
End Class
相互運用クラスは、単純にオートメーション オブジェクトを作成し、スリープしてから終了します (ただし、Word の場合は、より複雑なテストを完了しました)。
コンソール アプリを実行すると、(タスク マネージャーを介して) 2 つの WINWORD.EXE プロセスが並行して作成され、1 つの INFOPATH.EXE プロセスのみが作成されていることがわかります。実際、InfoPathTest の最初のインスタンスが ip.Quit を呼び出すと、INFOPATH.EXE プロセスが終了します。InfoPathTest の 2 番目のインスタンスが ip.Quit を呼び出すと、DCOM タイムアウト例外がスローされます。つまり、2 つのインスタンスが同じ基になるオートメーション オブジェクトを共有しているように見え、ip.Quit の最初の呼び出しの後、そのオブジェクトは存在しなくなります。
この段階では、ユーザー ログインごとにサポートされる INFOPATH.EXE は 1 つだけだと思いました。Windows サービスを拡張して 2 つの新しいプロセス (InfoPathTest と呼ばれるコンソール アプリケーション) を開始し、それぞれが異なるユーザー アカウントで実行されるようにしました。これらの新しいプロセスは、INFOPATH.EXE の自動化を試みます。
ここが興味深いところです。これは実際に機能しますが、一部のマシンでのみ機能し、なぜそうなのかわかりません。
そしてサービスコード ( AsproLockの助けを借りて):
public partial class InfoPathService : ServiceBase {
private Thread _mainThread;
private bool isStopping = false;
public InfoPathService() {
InitializeComponent();
}
protected override void OnStart(string[] args) {
if (_mainThread == null || _mainThread.IsAlive == false) {
_mainThread = new Thread(ProcessController);
_mainThread.Start();
}
}
protected override void OnStop() {
isStopping = true;
}
public void ProcessController() {
while (isStopping == false) {
try {
IntPtr hWinSta = GetProcessWindowStation();
WindowStationSecurity ws = new WindowStationSecurity(hWinSta, System.Security.AccessControl.AccessControlSections.Access);
ws.AddAccessRule(new WindowStationAccessRule("user1", WindowStationRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
ws.AddAccessRule(new WindowStationAccessRule("user2", WindowStationRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
ws.AcceptChanges();
IntPtr hDesk = GetThreadDesktop(GetCurrentThreadId());
DesktopSecurity ds = new DesktopSecurity(hDesk, System.Security.AccessControl.AccessControlSections.Access);
ds.AddAccessRule(new DesktopAccessRule("user1", DesktopRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
ds.AddAccessRule(new DesktopAccessRule("user2", DesktopRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
ds.AcceptChanges();
ThreadPool.QueueUserWorkItem(Process1);
ThreadPool.QueueUserWorkItem(Process2);
} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine(String.Format("{0}: Process Controller Error {1}", System.Threading.Thread.CurrentThread.ManagedThreadId, ex.Message));
}
Thread.Sleep(15000);
}
}
private static void Process1(object context) {
SecureString pwd2;
Process process2 = new Process();
process2.StartInfo.FileName = @"c:\debug\InfoPathTest.exe";
process2.StartInfo.UseShellExecute = false;
process2.StartInfo.LoadUserProfile = true;
process2.StartInfo.WorkingDirectory = @"C:\debug\";
process2.StartInfo.Domain = "DEV01";
pwd2 = new SecureString(); foreach (char c in "password") { pwd2.AppendChar(c); };
process2.StartInfo.Password = pwd2;
process2.StartInfo.UserName = "user1";
process2.Start();
process2.WaitForExit();
}
private static void Process2(object context) {
SecureString pwd2;
Process process2 = new Process();
process2.StartInfo.FileName = @"c:\debug\InfoPathTest.exe";
process2.StartInfo.UseShellExecute = false;
process2.StartInfo.LoadUserProfile = true;
process2.StartInfo.WorkingDirectory = @"C:\debug\";
process2.StartInfo.Domain = "DEV01";
pwd2 = new SecureString(); foreach (char c in "password") { pwd2.AppendChar(c); };
process2.StartInfo.Password = pwd2;
process2.StartInfo.UserName = "user2";
process2.Start();
process2.WaitForExit();
}
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetProcessWindowStation();
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetThreadDesktop(int dwThreadId);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern int GetCurrentThreadId();
}
InfoPathTest.exe プロセスは、前述の InfoPathTest.Test() メソッドを呼び出すだけです。
要約すると、これは機能しますが、特定のマシンでのみ機能します。失敗すると、2 番目の INFOPATH.EXE プロセスが実際に作成されますが、終了コード 0 ですぐに終了します。イベント ログには何も記録されず、コードにも例外はありません。
動作中のマシンと動作していないマシンを区別するために多くのことを調べましたが、今は行き詰まっています。
特に、複数の InfoPath インスタンスを並行して自動化する方法について他の考えがある場合は、ポインタを高く評価します。