Tuer processus cachés
Bonjour à tous, bonjour le fofo,
J'ai développé un outil à la base sur XP et Excel 2003 qui fonctionne très bien. Dans cet outil j'ouvre certains classeurs en caché (bases de donnée) pour importer certaines informations dans ma feuille principale. Les classeurs cachés sont ensuite fermés par un code que j'ai récupéré sur le net et qui ventile tous les processus Excel et si la fenêtre de ce dernier est cachée, le processus est fermé automatiquement.
Le problème c'est que ce code en question (qui fonctionne bien sur XP) ne fonctionne plus sur Windows 7 (aussi bien 32bits que 64bits d'ailleurs). Du coup, en cherchant bien, j'en ai trouvé un qui fonctionne et qui tue les processus sous Windows 7, sauf qu'il ne fait pas la différence entre fenêtre visible et fenêtre invisible. Évidemment j'ai besoin qu'il ne ferme que les sessions d'excel ou word invisibles à l'utilisateur.
Ci-dessous le code qui fonctionne aussi bien sur xp et win7 :
Option Explicit
Type strucPROCESSENTRY32
dwSize As Long ' DWORD : Size of the structure, in bytes
cntUsage As Long ' DWORD : not used (0)
th32ProcessID As Long ' DWORD : PID
th32DefaultHeapID As Long ' ULONG_PTR : not used (0)
th32ModuleID As Long ' DWORD : not used (0)
cntThreads As Long ' DWORD : Threads
th32ParentProcessID As Long ' DWORD : parent process ID
pcPriClassBase As Long ' LONG
dwFlags As Long ' LONG : not longer used (0)
szExeFile As String * 512 ' TCHAR szExeFile[MAX_PATH] name of the executable file for the process
End Type
Const TH32CS_SNAPPROCESS As Long = &H2
Const PROCESS_TERMINATE As Long = &H1
Const PROCESS_QUERY_INFORMATION As Long = &H400
Private Declare Function CreateToolhelp32Snapshot Lib "Kernel32.dll" (ByVal dwFlags As Long, ByVal th32ProcessID As Long) As Long
Private Declare Function Process32First Lib "Kernel32.dll" (ByVal hSnapshot As Long, ByRef lppe As strucPROCESSENTRY32) As Long
Private Declare Function Process32Next Lib "Kernel32.dll" (ByVal hSnapshot As Long, ByRef lppe As strucPROCESSENTRY32) As Long
Private Declare Function OpenProcess Lib "Kernel32.dll" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessID As Long) As Long
Private Declare Function CloseHandle Lib "Kernel32.dll" (ByVal hObject As Long) As Long
Private Declare Function TerminateProcess Lib "Kernel32.dll" (ByVal hProcess As Long, ByVal dwExitCode As Long) As Long
Sub TermProcess(strProcessName As String)
Dim hSnapshot As Long
Dim lppe As strucPROCESSENTRY32
Dim hProc As Long
Dim Retval As Long
Dim strPrcsName As String
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
lppe.dwSize = Len(lppe)
Retval = Process32First(hSnapshot, lppe)
Do While Retval
strPrcsName = Left(lppe.szExeFile, InStr(1, lppe.szExeFile, vbNullChar) - 1)
Debug.Print strPrcsName, lppe.th32ProcessID
If strPrcsName Like strProcessName Then
hProc = OpenProcess(PROCESS_TERMINATE, 0, lppe.th32ProcessID)
If hProc <> 0 Then
Retval = TerminateProcess(hProc, 0)
' Si Retval=0 échec de la fonction TerminateProcess(..)
Call CloseHandle(hProc)
End If
End If
' Process Suivant
Retval = Process32Next(hSnapshot, lppe)
Loop
Call CloseHandle(hSnapshot)
End Sub
Function KillProcess(pLng_ProcessId As Long)
Dim hProc As Long
Dim Retval As Long
hProc = OpenProcess(PROCESS_TERMINATE, 0, pLng_ProcessId)
If hProc <> 0 Then
Retval = TerminateProcess(hProc, 0)
' Si Retval=0 échec de la fonction TerminateProcess(..)
Call CloseHandle(hProc)
End If
End Function
Private Sub test()
'Nécessite d'activer la référence Microsoft Office Word
Dim oAppWord As Word.application
Dim oDocWord As Word.Document
Set oAppWord = New Word.application
Set oDocWord = oAppWord.Documents.Add
With oAppWord
.Visible = False
.WindowState = wdWindowStateMaximize
End With
TermProcess "WINWORD.EXE"
End SubSi quelqu'un a une idée sur "comment détecter la visibilité de la fenêtre d'un processus particulier", je suis preneur. Merci beaucoup
Bonjour,
J'ai bien ma petite idée sur ton problème mais il faudrait confirmation.
Tu peu montrer le code qui ouvre et ferme tes classeurs ?
Tu dis.
Ci-dessous l'ancien code qui faisait cette tâche (fonctionne bien sur XP mais par sur Win7)
Je lançais la procédure sub_fermerExcelInvisible() pour supprimer tous les processus cachés d'Excel
Option Explicit
'API ouverture processus et ses constantes
Public Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessID As Long) As Long
Public Const PROCESS_VM_READ As Long = (&H10)
Public Const PROCESS_QUERY_INFORMATION As Long = (&H400)
Public Declare Function GetCurrentProcessId Lib "Kernel32.dll" () As Long
Declare Function IsWindowVisible Lib "user32" (ByVal Hwnd As Long) As Long
Declare Function GetModuleFileNameEx Lib "psapi.dll" Alias "GetModuleFileNameExA" (ByVal hProcess As Long, ByVal hModule As Long, ByVal lpFilename As String, ByVal nSize As Long) As Long
Declare Function EnumWindows Lib "user32" (ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
Declare Function GetWindowThreadProcessId Lib "user32" (ByVal Hwnd As Long, lpdwProcessId As Long) As Long
Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal Hwnd As Long, ByVal lpClassName As String, ByVal nmaxCount As Long) As Long
Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal Hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Declare Function TerminateProcess Lib "kernel32" (ByVal hProcess As Long, ByVal uExitCode As Long) As Long
Const PROCESS_TERMINATE As Long = &H1
Public Const MAX_PATH As Long = 260
Public hProcess As Long 'handle du processus
Public CurrentProcessId As Long, ViewOnly As Boolean
Public ExcelAppli As Object, OpFichier As Object, bob As Long
Sub sub_fermerExcelInvisible()
'-------------------------------------------------------------------------
' Ferme les fichiers excel cachés et ouverts en lecture seule (ex: la bdd)
'-------------------------------------------------------------------------
Dim lRet As Long
Dim lParam As Long
CurrentProcessId = GetCurrentProcessId()
lRet = EnumWindows(AddressOf EnumWinProc, lParam)
End Sub
Function EnumWinProc(ByVal lhWnd As Long, ByVal lParam As Long) As Long
'-----------------------------------
'enumeration des processus
'renvoie du/des session(s) excel
'Arret du/des Processus excel caché
'-----------------------------------
Dim Retval As Long, ProcessID As Long, ThreadID As Long
Dim WinClassBuf As String * 255, WinTitleBuf As String * 255
Dim WinClass As String, WinTitle As String, NomExe As String
' see the Windows Class and Title for each top level Window
Retval = GetClassName(lhWnd, WinClassBuf, 255)
WinClass = StripNulls$(WinClassBuf) ' remove extra Nulls & spaces
Retval = GetWindowText(lhWnd, WinTitleBuf, 255)
WinTitle = StripNulls$(WinTitleBuf)
' la fenetre(thread principale)est elle visible ?
Retval = IsWindowVisible(lhWnd)
' on recupere l'PID de la fenetre(thread principale)
ThreadID = GetWindowThreadProcessId(lhWnd, ProcessID)
' on recupere le nom du thread principale
NomExe = GetProcessFileName(ProcessID)
If NomExe = "excel.exe" And WinTitle Like "Microsoft Excel*" = True Then
' si le thread principale d'excel est invisible
If Retval = 0 Then
If ViewOnly = False Then
' on arrete le processus
' La fonction renvoie 1 si le processus c'est arrete
If CloseProcess(ProcessID) = 1 Then
Else
MsgBox "le processus excel.exe invisible n'a pas pu etre arreté ! ", vbExclamation
End If
End If
End If
End If
CloseHandle hProcess
EnumWinProc = True
End Function
Public Function StripNulls(OriginalStr As String) As String
' This removes the extra Nulls so String comparisons will work
If (InStr(OriginalStr, Chr(0)) > 0) Then
OriginalStr = Left(OriginalStr, InStr(OriginalStr, Chr(0)) - 1)
End If
StripNulls = OriginalStr
End Function
Public Function GetProcessFileName(ByVal ProcessID As Long) As String
' Processus 0
If ProcessID = 0 Then
GetProcessFileName = "[System Process]"
' Processus 4
ElseIf ProcessID = 4 Then
GetProcessFileName = "System"
Else
' On cherche son chemin d'accès complet
'Dim hProcess As Long 'handle du processus
Dim hModule As Long 'handle du module de l'exe
Dim ret As Long 'résultat
' On demande un handle pour le processus
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION Or PROCESS_VM_READ Or PROCESS_TERMINATE, 0&, ProcessID)
' Si erreur (accès refusé)
If hProcess Then
' On préformate la chaine
GetProcessFileName = Space(MAX_PATH)
' On récupère son nom complet
GetModuleFileNameEx hProcess, 0, GetProcessFileName, MAX_PATH
' On ferme le handle ouvert
'CloseHandle hProcess
' On retire le vbNUllChar de fin de chaine
'-----------------------------------
'LES LIGNES CI-DESSOUS BUGGUENT SUR WINDOWS 7
'-----------------------------------
GetProcessFileName = Left(GetProcessFileName, InStr(GetProcessFileName, vbNullChar) - 1)
'on extrait le nom de l'Image du processus
GetProcessFileName = LCase(Right(GetProcessFileName, InStr(1, StrReverse(GetProcessFileName), "\") - 1))
Exit Function
ElseIf hProcess = 0 Then
GetProcessFileName = vbNullString
End If
End If
End Function
Public Function CloseProcess(ProcessID As Long) As Long
'fermeture du thread principal d'excel
CloseProcess = TerminateProcess(hProcess, 0)
End FunctionTu ne donne que les procédures pour tuer les processus mais ce n'est pas cela que je demandais.
Chrix a écrit :j'ouvre certains classeurs en caché (bases de donnée) pour importer certaines informations dans ma feuille principale
C'est ce code que je voudrais voire.
Pourquoi employer un procédé aussi complexe pour extraire des données d'autre classeurs ?
Tu dis.
Si ca peux te rassurer, j'ouvre bien et je ferme bien tous mes objets et autres classeurs externes par le code normal. Le problème c'est que si je rencontre un bug quelconque avant d'agir sur la destruction de mon objet, mon objet se détruit ou n'existe plus. Du coup je ne peux plus le fermer correctement et le processus reste actif. C'est pour cela que j'utilisais cette fonction afin de bien tout nettoyer et être sûr qu'il n'existe plus aucun processus caché. regarde la procédure sub_deconnexionBdd() pour mieux comprendre.
Exemple :
Option Explicit
Sub sub_connexionBdd()
'-------------------------------------------------------------------------------------------
' Procédure de connexion à la base de donnée
'-------------------------------------------------------------------------------------------
On Error Resume Next
'Déclarations
Dim sCheminBdd As String, sRepBdd As String
Dim bConnexion As Boolean
Dim rRepertoireBdd As Range, rCell As Range
'Chargement des variables globales
sub_chargerVariable
'Affectation et liste des répertoires possibles
Set rRepertoireBdd = ThisWorkbook.Worksheets("param_program").[paramListeRepertoireBdd2]
'Création de l'objet BDD
Set gv_oBDDapp = CreateObject("Excel.Application")
'Tentative de connexion dans tous les répertoires possibles
'Si un fichier est trouvé => sortie de la boucle
bConnexion = False
For Each rCell In rRepertoireBdd
sRepBdd = rCell.Value
sCheminBdd = rCell.Value & [paramFichierBdd].Value
Set gv_oBDD = gv_oBDDapp.Workbooks.Open(sCheminBdd, ReadOnly:=True)
If err = 0 Then
bConnexion = True
Exit For
Else
err.Clear
End If
Next rCell
On Error GoTo 0
'Resultat
If Not bConnexion Then
[paramFichierActuelBdd].Value = vbNullString
[paramRepertoireActuelBdd].Value = vbNullString
[paramVerifFichierBdd].Value = vbNullString
MsgBox "Impossible d'ouvrir la base de donnée." & vbNewLine & "Le fichier est ouvert mais sera totalement inutilisable", vbCritical
Else
'stockage du chemin accessible dans les paramètres de la feuille
[paramFichierActuelBdd].Value = sCheminBdd
[paramRepertoireActuelBdd].Value = sRepBdd
[paramVerifFichierBdd].Value = [paramFichierBdd].Value
End If
End Sub
Sub sub_deconnexionBdd()
'-------------------------------------------------------------------------------------------
' Procédure de déconnexion à la base de donnée, (fermer le fichier invisible)
'-------------------------------------------------------------------------------------------
If gv_bErreurPerso Then On Error GoTo erreur
On Error Resume Next
If Not gv_oBDD Is Nothing Then
gv_oBDD.Close False
gv_oBDDapp.Quit
Set gv_oBDD = Nothing
Set gv_oBDDapp = Nothing
On Error GoTo erreur
Else
sub_fermerExcelInvisible
End If
Exit Sub
erreur:
sub_gestionnaireErreur err, "mod_bdd", "sub_deconnexionBdd"
End SubVoilà pourquoi je voudrais pouvoir fermer les processus cachés coute que coute.
Chrix a écrit :Si ca peux te rassurer, j'ouvre bien et je ferme bien tous mes objets et autres classeurs externes par le code normal
Alors là.. si tu trouve ça un code normal pour ouvrir et fermer des classeurs je pense que tu est non seulement à côté de la plaque mais à côté de la planète.
Pour un "code normal", toutes tes procédure peuvent être réduites à une ou deux dizaine de lignes.
Si tu est intéressé je peu voir pour optimiser une procédure. Sinon beh... je ne peu t'aider plus.
Tu dis.
mdrr !!
Bon lermite, c'est gentil mais je crois que tu m'as apporté le maximum que tu pouvais faire, hein on va pas insister.
Mais attention quand même à être respectueux des autres.
Si je peux te donner un conseil, essaie de bien comprendre/lire les problèmes des autres (c'est important pour bien répondre en fait) avant de porter des jugements ou d'affirmer "arbitrairement" (pour ne pas dire autre chose hein, je suis poli moi) qu'une bonne procédure est une procédure avec peu de lignes...
D'autant que je ne cherche pas à optimiser une procédure mais surtout en avoir une autre qui d'abord fonctionne aux exigences de Windows 7.
Mais merci quand même.
Bref, ya t'il un spécialiste des API x64 qui pourrait m'aider ?
Je ne vois pas où j'ai été impoli, j'ai juste donné mon avis sur ta façon de faire, mais si cela t'a froisser tu voudras bien m'excuser ?
Et tu peu être mort de rire jusqu'a la saint glinglin c'est bien la toute première fois que je vois employer des api pour ouvrir des classeurs Excel., pourtant crois moi... les API.. ça me connais, j'ai bien décodé toutes tes magnifiques procédures.
Mais il faut reconnaître que tout le monde ne peu pas être aussi calé que toi.
Cordialement.
Bon, déjà j'utilise pas cet API pour ouvrir des classeurs mais simplement pour fermer les sessions d'Excel qui resteraient ouvertes suite à un bug.
Si tu parles d'un workbooks.open ou d'un workbook.close ou app.quit, je le fais déjà pour créer/détruire mon objet (c'est pour ca que je te demandais de regarder ma fonction sub_deconnexionBdd()... )
Bref, je n'utilise cet API si et seulement si mon objet est détruit/perdu suite à un bug. La connexion avec la base de donnée est persistante (toujours en fond de tache) et c'est pour ça que je veux qu'elle soit cachée (et c'est d'ailleurs pour ça que j'utilise CreateObject("Excel.Application") pour créer mon objet)
Donc si tu veux mon problème n'est pas un problème de conception, mais un problème d'incompatibilité avec Win 7 sur une vieille procédure que je veux remplacer par une nouvelle (que j'ai déjà trouvé, voir plus haut) mais qui ne gère pas la fermeture des processus cachés.
Voilà, telle était ma question de départ que j'aimerais bien élucider.
Bon, puisque tu répond, restons constructif et oublions les deux postes précédant.
Chrix a écrit :Voilà, telle était ma question de départ.
J'avais très bien compris ta question de départ (sinon je ne serais pas intervenu) et le code qui va avec, je n'ai pas répondu dans ce sens parce que je n'ai pas encore eu l'occasion de faire des recherches sur ce point bien précis et que je trouvais ( et trouve toujours ) que ton approche n'est pas la bonne.
Tu crée une nouvelle instance d'Excel pour chaque classeur qui fait partie de ta BD.. Pourquoi ? alors qu'il serait si facile de l'ouvrir dans la même instance que le classeur principal ? . Si une erreur devait se produire... disons ouvrir un classeur qui n'existe pas/plus le traitement d'erreur local aurais vite fait d'y remédier et il n'y aurait pas d'instance d'Excel qui... traînerait.
Qu'en pense-tu ?
Oui, restons constructif.
Pour résumer et situer rapidement ce que fais mon application. C'est donc une application de chiffrage, il y a plusieurs fichiers excel qui ont été créées par des commerciaux mais dans un format toujours uniforme. Ce sont des fichiers d'articles (avec référence, prix de revient, quantité etc) et il y en a autant de fichiers qu'il y a de catégorie d'article. Alors en soi, ce ne sont pas de vraies bases de donnée mais elles ont le mérite d'être standardisées.
Avant ils allaient chercher les articles en ouvrant les BDD une par une et copiaient collaient leur bloc d'article dans leur grille de chiffrage finale.
Donc ça c'était avant. Désormais, j'ai automatisé tout ça en leur créant, dans un fichier excel principal, un module de recherche (style google) qui leur permet facilement de trouver/d'importer des articles (et leur éviter ainsi l'ouverture manuelle du fichier et le copier-coller d'article)
C'est à ce moment que mon code intervient. Lorsque le commercial fait une recherche depuis son onglet de chiffrage, le code ouvre une nouvelle instance d'excel en invisible avec le fichier en lecture seule, scanne les articles qui correspond à sa recherche et lui propose les résultats avant de se fermer. C'est très important (et j'insiste ^^) que le fichier d'article reste caché afin que le commercial reste sur son onglet et ne soit pas perturbé par un fichier qui s'ouvre et qui se ferme à chaque fois qu'il fait une recherche. Tout doit être le plus transparent possible pour l'utilisateur.
Seulement voilà, autant ca marche impeccable sous Win XP autant j'ai constaté que sous windows 7 certains bugs mineurs font parfois planter la recherche de l'utilisateur et même avec une gestion d'erreur personnalisée, certaines sessions excel cachées ne se ferment pas correctement. Le temps de corriger tous ces petits bugs, j'ai trouvé cette parade par l'API qui en cas de problème inattendu (si l'objet n'existe plus) permet de supprimer les sessions d'excel qui trainent.
Alors évidemment, je fais peut être fausse route, peut être qu'il y a moyen d'ouvrir un classeur excel masqué dans la session active de l'utilisateur mais tout mon code est basé sur ce système et si je pouvais ne pas tout réécrire, ça serait un gros gain de temps.
Cela dit et maintenant que le problème est posé plus clairement, comment procèderais tu pour arriver aux mêmes fins avec une seule instance active ?
Je ferais comme ça..
Copie les deux classeurs dans le même répertoire et ouvre le classeur Principale.xls
J'ai mis en xls vu que je ne sais pas si ta version d'Excel est encore 2003 ou +
Et clic sur le bouton.
Attention, quelque restriction:
Si les noms de feuilles des BD change il faudra la mettre en plus, tu dis.
Les plages sont passées en string, les plages réel étant difficile à adapter sur des classeurs invisible mais en principe ça ne pose aucun problème.
Tu pourrais éventuellement passer la sub en fonction avec les données en paramètre, pour que ce soit clair j'ai laissé en sub.
Bien entendu ce n'est qu'une démo, il faudra l'adapter à tes besoins.
Tu dis.
Ok j'ai bien compris le principe.
Toutefois, j'ai très peur concernant ce ActiveWindow.Visible = False qui à l'instant viens de cacher mon classeur principal (que j'étais en train de triturer). Pour peu qu'elle soit combinée avec un screenupdating = false, on ne peut plus agir sur rien. Si cela arrive à un des mes collaborateurs, je vais passer à la moulinette
Je ne vais pas prendre le risque de l'utiliser. Cela dit, si tout est bien maitrisé, c'est une solution qui marche.
Merci de m'avoir présenté cette solution que je ne connaissais pas.
Evidemment, si tu est en train de "triturer" les instances sont perdues. Tes utilisateurs vont-ils aller Triturer ton code ?
MAIS OK, je comprend, pourquoi faire simple quand il y a moyen de faire compliquer.
A++
lol
Dans le monde professionnel, j'ai appris une chose importante " les utilisateurs sont capables de tout ", et si ce genre de problème m'arrive à moi, il peux arriver à tout le monde. Et puis je ne suis qu'un humain, le code parfait n'existe pas, on fait toujours des erreurs et je ne suis pas à l'abri d'un activewindow mal placé, mal maitrisé. En tout cas, même en allant au devant de tout ce qu'un utilisateur peut faire, il y a toujours une situation que l'on a pas prévu.
Je ne critique pas le code que tu as fourni, il marche très bien dans ta démo (et je t'ai d'ailleurs remercié pour cela...) mais je n'ai pas la volonté de modifier une centaine de procédure sur une fonction que, pour l'instant, je ne maitrise pas.
C'est simplement un choix par rapport à mon projet déjà existant et à mon habitude de ne jamais faire confiance aux objets de type 'active' dans ma programmation (qui m'ont souvent causé des problèmes).
J'ai trouvé la solution, inclure le code ci-dessous dans un module :
Option Explicit
Declare Function FindWindow Lib "USER32" Alias "FindWindowA" (ByVal lpClassName As Long, ByVal lpWindowName As Long) As Long
Declare Function GetWindowText Lib "USER32" Alias "GetWindowTextA" (ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Declare Function GetWindow Lib "USER32" (ByVal hWnd As Long, ByVal wCmd As Long) As Long
Declare Function IsWindowVisible Lib "USER32" (ByVal hWnd As Long) As Long
Declare Function PostMessage Lib "USER32" Alias "PostMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Public Const WM_QUIT = &H12
Sub FermerFenetreInvisible(sNomFenetre As String)
'---------------------------------------------------------------------------------------
' Procedure : FermerFenetreInvisible
' Date : 16/04/2012
' Purpose : Ferme les fenêtres cachées avec une partie du nom de fenêtre passé en argument (sans le "voulez vous enregistrer..." éventuel)
'---------------------------------------------------------------------------------------
Dim titre As String
Dim hWnd As Long
hWnd = FindWindow(0, 0)
Do While hWnd <> 0
If IsWindowVisible(hWnd) = 0 Then
'Si n'est pas visible => récupération du nom de la fenêtre
titre = String(100, Chr$(0))
GetWindowText hWnd, titre, 100
titre = Left$(titre, InStr(titre, Chr$(0)) - 1)
If LCase(titre) Like "*" & LCase(sNomFenetre) & "*" Then
'Fenêtre correspondante trouvée
'Fermeture sans le "voulez vous enregistrer..." grace à la constante WM_QUIT
PostMessage hWnd, WM_QUIT, 0&, 0&
End If
End If
hWnd = GetWindow(hWnd, 2)
Loop
End Sub
Sub testFermetureWord()
'Nécessite la référence Microsoft Word x.0 object library
Dim oAppWord As Word.application
Dim oAppWord2 As Word.application
Set oAppWord = CreateObject("Word.Application")
Set oAppWord2 = CreateObject("Word.Application")
'on met une application visible et une autre invisible
oAppWord2.Visible = True
oAppWord.Visible = False
MsgBox "ouvrez le gestionnaire des tâches"
FermerFenetreInvisible "microsoft word"
End Sub