Sto tentando di automatizzare più istanze parallele di Office InfoPath 2010 tramite un servizio di Windows. Comprendo che l'automazione di Office da un servizio non è supportata, tuttavia è un requisito del mio cliente.Istanze di automazione di interoperabilità multiple di InfoPath
Posso automatizzare altre applicazioni di Office in modo parallelo, tuttavia InfoPath si comporta diversamente.
Quello che ho trovato è che ci sarà sempre un'istanza del processo INFOPATH.EXE creato, indipendentemente dal numero di chiamate parallele a CreateObject("InfoPath.Application")
. Diversamente, è possibile creare più istanze di WINWORD.EXE tramite il meccanismo simile CreateObject("Word.Application")
Per riprodurre questo problema, è possibile utilizzare una semplice applicazione di console.
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();
}
Le classi di InfoPathTest e WordTest (VB) si trovano in un altro progetto.
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
Le classi di interoperabilità semplicemente creare gli oggetti di automazione, il sonno e poi uscire (anche se nel caso di Word, ho completato prove più complesse).
Quando si esegue l'app della console, è possibile visualizzare (tramite Task Manager) due processi WINWORD.EXE creati in parallelo e un solo processo INFOPATH.EXE creato. Infatti quando la prima istanza di InfoPathTest chiama ip.Quit, termina il processo INFOPATH.EXE. Quando la seconda istanza di InfoPathTest chiama ip.Quit, viene generata un'eccezione di timeout DCOM - sembra che le due istanze condividessero lo stesso oggetto di automazione sottostante e quell'oggetto non esista più dopo la prima chiamata a ip.Quit.
In questa fase i miei pensieri erano solo un singolo INFOPATH.EXE è supportato per accesso utente. Ho espanso il servizio di Windows per avviare due nuovi processi (un'applicazione console chiamata InfoPathTest), ognuno con un account utente diverso. Questi nuovi processi tenterebbero quindi di automatizzare INFOPATH.EXE
Ecco dove diventa interessante, questo in realtà funziona, ma solo su alcune macchine, e non riesco a capire perché sia così.
E il codice di servizio (con l'aiuto di 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();
}
Il processo InfoPathTest.exe chiama semplicemente il metodo InfoPathTest.Test() descritto sopra.
In breve, questo funziona, ma solo su alcune macchine. Quando non riesce, il secondo processo INFOPATH.EXE viene effettivamente creato, ma si chiude immediatamente con un exitcode di 0. Non c'è nulla nei registri eventi, né alcuna eccezione nel codice.
Ho esaminato molte cose per cercare di distinguere tra macchine funzionanti/non funzionanti, ma ora sono bloccato.
Tutti i puntatori sono apprezzati, soprattutto se si hanno altri pensieri su come automatizzare più istanze di InfoPath in parallelo.
Il nostro attuale approccio di automazione è sincrono, tuttavia per motivi di prestazioni stiamo cercando di consentire processi paralleli. – user1369371