Get all excel application

Update

I’ve created a new article that describes how to do the same things as this article, but with a reusable assembly instead of everything in one class, and there is a demo proejct and walkthrough as well.  Check it out here.


Introduction

If you write a lot of .NET Excel automation, particularly for use on servers where there may be multiple instances of Excel running at a time, you may find yourself needing to access a specific instance of Excel, not just the «active» one.

I have spent a lot of time on forums and Q&A sites trying to find a way to iterate over all running instances, and select specific instances by Hwnd or ProcessID, but have not yet found a satisfactory article. After a while of piecing different answers together, I believe I have a class that can provide this functionality for anyone in a similar situation.

A lot of credit goes to the anonymous article at the link below, as well as some hints from various users of StackOverflow.

http://pastebin.com/F7gkrAST

Background

You will need an intermediate grasp of C# for this project, and some familiarity with Windows processes and window handles. There is also some usage of LINQ and lambda expressions, but only in a few places. You actually don’t need to know much about Excel automation, other than knowing what the Microsoft.Office.Interop.Excel.Application class is.

Several parts of the private implementation of the class involve extern calls to the Win32 API, which you don’t necessarily need to understand to use this class.  I am not very familiar with the Win32 API myself, but learned a good bit about it in putting this class together. 

Please let me know if the code violates any best practices for dealing with Win32.

IMPORTANT: This code has not been tested on all versions of Excel or Windows.  (Please help me test them all out.) I believe this code may be particularly prone to issues based on different Excel and Windows versions.

Tested environments:

  • Windows 7 64-bit with Excel 2016 32-bit
  • Windows 7 64-bit with parallel instances of Excel 2010 32-bit and Excel 2013 32-bit.

Using the Code

The class below can be used alongside the Microsoft primary interop assembly for Excel, to get a Microsoft.Office.Interop.Excel.Application object for any running instance of Excel.

I’ve split the class into two partial class files, to breakup what would otherwise be a 200-line file. The first part is the public interface, and the second is the private implementation.

Public Interface

The publicly visible interface is pretty simple, and has the following members:

  • Constructor — This takes a nullable Int32 as a parameter, which defaults to null.  The value is used to filter Excel instances by Windows sessionID.  If null, the class’s SessionID property will be set to the current sessionID.
  • SessionID — This property is used to filter Excel instances by Windows sessionID. This is very important when working with servers where multiple users may be using Excel at once.
    • If -1, the collection will give access to instances from all sessions.
    • If a valid sessionID, the collection will give access to all Excel instances running in that session.
    • If not a valid sessionID, the collection will always be empty. No exception is thrown.
  • Accessors
    • FromProcess — This method takes a reference to a Process and returns the Excel instance of that Process, or null if the Process is not an Excel instance.
    • FromProcessID — This method takes a processID and returns the Excel instance of the corresponding Process, or null if the ID is invalid or does not correspond to an Excel instance.
    • FromMainWindowHandle — This method takes the Hwnd value of the main window of an Excel instance, and returns the corresponding Excel instance, or null if the Hwnd is invalid or does not correspond to an Excel instance.
    • PrimaryInstance — This property returns the first-created Excel instance, or null if there are none.  If a user double-clicks an Excel file icon, this will be the instance the file opens in.
    • TopMostInstance — This property returns the Excel instance with the top-most visible window, or null if there are none. This will normally be the last instance selected by a user.
  • Methods
    • GetEnumerator — This method returns a collection of all Excel instances, filtering by SessionID (if SessionID is not -1).
    • GetProcesses — This method returns a collection of all Process objects of Excel instances, filtering by SessionID (if SessionID is not -1).
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;


using xlApp = Microsoft.Office.Interop.Excel.Application;

namespace ExcelExtensions {

                public partial class ExcelAppCollection : IEnumerable<xlApp>  {

        #region Constructors

                                        public ExcelAppCollection (Int32? sessionID = null) {
            if (sessionID.HasValue && sessionID.Value < -1)
                throw new ArgumentOutOfRangeException("sessionID");

            this.SessionID = sessionID
                ?? Process.GetCurrentProcess().SessionId;
        }

        #endregion

        #region Properties

                                public Int32 SessionID { get; private set; }

        #endregion

        #region Accessors

                                        public xlApp FromProcess(Process process) {
            if (process == null)
                throw new ArgumentNullException("process");
            return InnerFromProcess(process);
        }

                                public xlApp FromProcessID(Int32 processID) {
            try {
                return FromProcess(Process.GetProcessById(processID));
            }
            catch (ArgumentException) {
                return null;
            }
        }

                                public xlApp FromMainWindowHandle(Int32 mainHandle) {
            return InnerFromHandle(ChildHandleFromMainHandle(mainHandle));
        }

                                public xlApp PrimaryInstance {
            get {
                try {
                    return Marshal.GetActiveObject(MarshalName) as xlApp;
                }
                catch (COMException) {
                    return null;
                }
            }
        }

                        public xlApp TopMostInstance {
            get {
                var topMost = GetProcesses() 
                    .Select(p => p.MainWindowHandle) 
                    .Select(h => new { h = h, z = GetWindowZ(h) }) 
                    .Where(x => x.z > 0) 
                    .OrderBy(x => x.z) 
                    .First(); 

                return FromMainWindowHandle(topMost.h.ToInt32());
            }
        }

        #endregion

        #region Methods

                                                public IEnumerator<xlApp> GetEnumerator() {
            foreach (var p in GetProcesses())
                yield return FromProcess(p);
        }
        IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }

                        public IEnumerable<Process> GetProcesses() {

            IEnumerable<Process> result = Process.GetProcessesByName(ProcessName);

            if (this.SessionID >= 0)
                result = result.Where(p => p.SessionId == SessionID);

            return result;
        }

        #endregion
   }
}

Private Implementation

As mentioned in the introduction, I am not an expert on the Win32 API.  Parts of the private implementation are still a bit mysterious to me and may violate best practices for using it.  That being said, it has been reliable as far as I’ve used it.

  • Methods
    • InnerFromProcess — This method takes a reference to a Process and returns the corresponding Microsoft.Office.Interop.Excel.Application object.
    • ChildHandleFromMainHandle — This method takes the Hwnd of a Process or Application object and returns a child window’s Hwnd.
    • InnerFromHandle — This method takes the Hwnd of a child window of an Application object and returns the Application.
    • GetWindowZ — This method takes the Hwnd of a window and returns its z value.
    • EnumChildFunc — This method is used by the EnumChildWindows method to get child window Hwnds.
  • External Methods
    • AccessibleObjectFromWindow — This method takes the Hwnd of an Excel window, as well as some of the constants below, and returns (through its ref parameter) a reference to a Window object, which can then be used to get its parent Application object. 
      • It does not work if you pass it the value of a Application‘s Hwnd property; it must be a specific workbook’s window’s Hwnd.  This may only be the case on Excel 2013 or newer, where there is no main Excel window.
    • EnumChildWindows — This method takes the Hwnd of the main window of an Excel instance and an EnumChildCallback delegate as parameters, and returns (through its ref parameter) the Hwnd of a child window, which can be used by AccessibleObjectFromWindow.
    • GetClassName — This method is used by the EnumChildCallback delegate that is passed to EnumChildWindows.  I believe it gets the details of the Window class internally so that an Hwnd can be returned.
    • GetWindow — This method takes an Hwnd and a constant as parameters.  The constant used determines how to get other Hwnds based on the provided Hwnd.  Using GW_HWNDPREV returns the Hwnd of the window directly above (z position) the given Hwnd.  This is used to get the TopMostInstance.
  • Constants and Delegates
    • MarshalName — This constant is required to get the «active» instance (PrimaryInstance) from the System.Runtime.InteropServices.Marshal class.
    • ProcessName — This constant is required to get Excel processes by name from System.Diagnostics.Process.
    • ComClassName — This constant is required for EnumChildFunc method, which is used by the EnumChildWindow method from the Win32 API.
    • DW_OBJECTID — This constant is required for the AccessibleObjectFromWindow method from the Win32 API.
    • GW_HWNDPREV — This constant is required for getting window z (depth) values from the GetWindow method from the Win32 API.  I copied a bit of the Microsoft documentation into the code comments.
    • rrid — This pseudo-constant is required for the AccesibleObjectFromWindow from the Win32 API.
    • EnumChildCallback — This delegate is implemented by the EnumChildFunc method and is requied for the EnumChildWindow method from the Win32 API.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;


using xlApp = Microsoft.Office.Interop.Excel.Application;
using xlWin = Microsoft.Office.Interop.Excel.Window;

namespace ExcelExtensions {

   public partial class ExcelAppCollection {

        #region Methods

        private static xlApp InnerFromProcess(Process p) {
            return InnerFromHandle(ChildHandleFromMainHandle(p.MainWindowHandle.ToInt32()));
        }

        private static Int32 ChildHandleFromMainHandle(Int32 mainHandle) {
            Int32 handle = 0;
            EnumChildWindows(mainHandle, EnumChildFunc, ref handle);
            return handle;
        }

        private static xlApp InnerFromHandle(Int32 handle) {
            xlWin win = null;
            Int32 hr = AccessibleObjectFromWindow(handle, DW_OBJECTID, rrid.ToByteArray(), ref win);
            return win.Application;
        }

        private static Int32 GetWindowZ(IntPtr handle) {
            var z = 0;
            for (IntPtr h = handle; h != IntPtr.Zero; h = GetWindow(h, GW_HWNDPREV))
                z++;
            return z;
        }
       
        private static Boolean EnumChildFunc(Int32 hwndChild, ref Int32 lParam) { 
            var buf = new StringBuilder(128); 
            GetClassName(hwndChild, buf, 128); 
            if (buf.ToString() == ComClassName) { 
                lParam = hwndChild; 
                return false; 
            } 
            return true; 
        }
        
        #endregion

        #region Extern Methods

        [DllImport("Oleacc.dll")] 
        private static extern Int32 AccessibleObjectFromWindow(
            Int32 hwnd, UInt32 dwObjectID, Byte[] riid, ref xlWin ptr); 

        [DllImport("User32.dll")] 
        private static extern Boolean EnumChildWindows(
            Int32 hWndParent, EnumChildCallback lpEnumFunc, ref Int32 lParam); 

        [DllImport("User32.dll")] 
        private static extern Int32 GetClassName(
            Int32 hWnd, StringBuilder lpClassName, Int32 nMaxCount); 

        [DllImport("User32.dll")] 
        private static extern IntPtr GetWindow(IntPtr hWnd, UInt32 uCmd);

        #endregion

        #region Constants & delegates

        private const String MarshalName = "Excel.Application";

        private const String ProcessName = "EXCEL";

        private const String ComClassName = "EXCEL7";

        private const UInt32 DW_OBJECTID = 0xFFFFFFF0;

        private const UInt32 GW_HWNDPREV = 3;
        
        
        
        
        

        private static Guid rrid = new Guid("{00020400-0000-0000-C000-000000000046}");

        private delegate Boolean EnumChildCallback(Int32 hwnd, ref Int32 lParam);
        #endregion
    }
} 

Points of Interest

Please let me know if you find this class helpful (or terrible).  I’m especially interested in issues with older version of Excel (pre-2013), multiple versions of Excel on one machine, or multiple users on one server.  If you have any further insight into how the Win32 API is working behind the scenes, I would also like to know more about that.  Any feedback is highly appreciated.

Further Developments

I have recently started working on a WPF application called ExcelBrowser that allows users to easily browse through multiple Excel instances, their workbooks, and sheets.  Part of the implementation of this application is directly decended from the class described in this article.  Check it out at github.com/JamesFaix/ExcelBrowser.  Also, note that the solution uses C#6/.NET 4.6.1.  As of writing this, I also need to catch up on some code comments, so bear with me.

The parts related to this article are in the ExcelBrowser.Interop project of the solution.  All extern methods are encapsulated in the NativeMethods class, the Session class represents a collection of all running Applications and all running Processes with the name «Excel». AppFactory provides methods for getting specific Application instances.  Some other parts of this class are also in the ApplicationExtensionMethods and ProcessExtensionMethods classes.

History

  • Added «Further Developments» section 11/20/16
  • Posted 2/23/2016

I am a professional developer, but I really create software because it’s fun. I’ve always been interested in deconstructing complex systems, and software engineering has proven to be an inexhaustable supersystem of such systems. In the past I’ve also spent time focusing on music composition, audio engineering, electronics, game design, history, and philosophy.

My strongest languages are English and C#, in fact I’m a Microsoft Certified Professional for «Programming in C#». I do not have any certifications for English, so please trust me there. I’ve spent a lot of time working on Windows desktop applications, particularly for interacting with SQL Server or automating Microsoft Office programs, using technologies such as C#, VB.NET, VBA, T-SQL, WinForms, WPF, ADO.NET, the MS Office PIA’s, ExcelDNA, EPPlus, and Crystal Reports. I’ve also done some web development using JavaScript, HTML, CSS, TypeScript, ASP.NET, WCF, jQuery, and requirejs. I am very interested in functional programming (F#, Haskell, Clojure), and try to use C# and JavaScript in a «functional» way at times, but I haven’t had the opportunity to use a functional language for a serious project yet.

I use the following to check if two instances are running, and display a message. It could be altered to close other instance… This may be of help… I need code to return a specific instance, and return for use similar to GetObject(,»Excel.Application»)… I don’t think it possible though

 If checkIfExcelRunningMoreThanOneInstance() Then Exit Function

In module (some of the declarations are possible used for other code):

Const MaxNumberOfWindows = 10

Const HWND_TOPMOST = -1
Const SWP_NOSIZE = &H1
Const SWP_NOMOVE = &H2

 Type RECT
        Left As Long
        Top As Long
        Right As Long
        Bottom As Long
End Type

Public Declare Function ShowWindow Lib "user32" (ByVal hwnd As Long, ByVal nCmdShow As Long) As Long
Global ret As Integer
Declare Function GetWindow Lib "user32" (ByVal hwnd As Long, ByVal wCmd As Long) As Long
Private Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long
Public Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long
Declare Function GetKeyNameText Lib "user32" Alias "GetKeyNameTextA" (ByVal lParam As Long, ByVal lpBuffer As String, ByVal nSize As Long) As Long
Declare Function MapVirtualKey Lib "user32" Alias "MapVirtualKeyA" (ByVal wCode As Long, ByVal wMapType As Long) As Long
Declare Function GetDesktopWindow Lib "user32" () As Long
Public Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long
Public Declare Function GetParent Lib "user32" (ByVal hwnd As Long) As Long
      Private Declare Function FindWindow Lib "user32" _
         Alias "FindWindowA" _
         (ByVal lpClassName As String, _
         ByVal lpWindowName As String) As Long

     Private Const VK_CAPITAL = &H14
Private Declare Function GetKeyState Lib "user32" _
    (ByVal nVirtKey As Long) As Integer

Private Declare Function OpenProcess Lib "kernel32" ( _
    ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long

Private Declare Function CloseHandle Lib "kernel32" ( _
    ByVal hObject As Long) As Long

Private Declare Function EnumProcesses Lib "PSAPI.DLL" ( _
   lpidProcess As Long, ByVal cb As Long, cbNeeded As Long) As Long

Private Declare Function EnumProcessModules Lib "PSAPI.DLL" ( _
    ByVal hProcess As Long, lphModule As Long, ByVal cb As Long, lpcbNeeded As Long) As Long

Private Declare Function GetModuleBaseName Lib "PSAPI.DLL" Alias "GetModuleBaseNameA" ( _
    ByVal hProcess As Long, ByVal hModule As Long, ByVal lpFileName As String, ByVal nSize As Long) As Long

Private Const PROCESS_VM_READ = &H10
Private Const PROCESS_QUERY_INFORMATION = &H400

Global ExcelWindowName$   'Used to switch back to later


Function checkIfExcelRunningMoreThanOneInstance()
    'Check instance it is 1, else ask user to reboot excel, return TRUE to abort
    ExcelWindowName = excel.Application.Caption  'Used to switch back to window later

    If countProcessRunning("excel.exe") > 1 Then
        Dim t$
        t = "Two copies of 'Excel.exe' are running, which may stop in cell searching from working!" & vbCrLf & vbCrLf & "Please close all copies of Excel." & vbCrLf & _
        "   (1 Then press Alt+Ctrl+Del to go to task manager." & vbCrLf & _
        "   (2 Search the processes running to find 'Excel.exe'" & vbCrLf & _
        "   (3 Select it and press [End Task] button." & vbCrLf & _
        "   (4 Then reopen and use PostTrans"
        MsgBox t, vbCritical, ApplicationName
    End If
End Function

   Private Function countProcessRunning(ByVal sProcess As String) As Long
    Const MAX_PATH As Long = 260
    Dim lProcesses() As Long, lModules() As Long, N As Long, lRet As Long, hProcess As Long
    Dim sName As String
    countProcessRunning = 0
    sProcess = UCase$(sProcess)

    ReDim lProcesses(1023) As Long
    If EnumProcesses(lProcesses(0), 1024 * 4, lRet) Then
        For N = 0 To (lRet  4) - 1
            hProcess = OpenProcess(PROCESS_QUERY_INFORMATION Or PROCESS_VM_READ, 0, lProcesses(N))
            If hProcess Then
                ReDim lModules(1023)
                If EnumProcessModules(hProcess, lModules(0), 1024 * 4, lRet) Then
                    sName = String$(MAX_PATH, vbNullChar)
                    GetModuleBaseName hProcess, lModules(0), sName, MAX_PATH
                    sName = Left$(sName, InStr(sName, vbNullChar) - 1)
                    If Len(sName) = Len(sProcess) Then
                        If sProcess = UCase$(sName) Then
                            countProcessRunning = countProcessRunning + 1
                        End If
                    End If
                End If
            End If
            CloseHandle hProcess
        Next N
    End If

End Function

The I found:

Dim xlApp As Excel.Application
Set xlApp = GetObject("ExampleBook.xlsx").Application

Which gets the object if you know the name of the sheet currently active in Excel instance. I guess this could be got from the application title using the first bit of code. In my app I do know the filename.

The code you have provided is for getting the workbooks in the current application. I can have different Excel applications in my machine (each having one or many workbooks). I want to get all of them.

Try the code below. Not all my work as per the credit at the top. Suggest you use a new workbook and copy all of the code into a standard module and then run Sub GetAllWorkbookWindowNames()

Not the comments at the asterisk line re only output the workbook names or both workbook names and worksheet names.

‘Credit to:
‘http://stackoverflow.com/questions/2971473/can-vba-reach-across-instances-of-excel
‘(Modified by OssieMac to output to a worksheet in ThisWorkbook _
‘plus to output all workbooks open in each Excel Instance)

Declare Function FindWindowEx Lib «User32» Alias «FindWindowExA» _
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, _
ByVal lpsz2 As String) 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 IIDFromString Lib «ole32» _
(ByVal lpsz As Long, ByRef lpiid As UUID) As Long

Declare Function AccessibleObjectFromWindow Lib «oleacc» _
(ByVal hWnd As Long, ByVal dwId As Long, ByRef riid As UUID, _
ByRef ppvObject As Object) As Long

 
Type UUID ‘GUID
  Data1 As Long
  Data2 As Integer
  Data3 As Integer
  Data4(7) As Byte
End Type

 
Const IID_IDispatch As String = «{00020400-0000-0000-C000-000000000046}»
Const OBJID_NATIVEOM As Long = &HFFFFFFF0

 
Sub GetAllWorkbookWindowNames()

    On Error GoTo MyErrorHandler

 
    Dim hWndMain As Long
    hWndMain = FindWindowEx(0&, 0&, «XLMAIN», vbNullString)

 
    Do While hWndMain <> 0
        GetWbkWindows hWndMain
        hWndMain = FindWindowEx(0&, hWndMain, «XLMAIN», vbNullString)
    Loop

 
    Exit Sub

 
MyErrorHandler:
    MsgBox «GetAllWorkbookWindowNames» & vbCrLf & vbCrLf & «Err = » _
        & Err.Number & vbCrLf & «Description: » & Err.Description
End Sub

 
Private Sub GetWbkWindows(ByVal hWndMain As Long)
    On Error GoTo MyErrorHandler

 
    Dim hWndDesk As Long
    hWndDesk = FindWindowEx(hWndMain, 0&, «XLDESK», vbNullString)

 
    If hWndDesk <> 0 Then
        Dim hWnd As Long
        hWnd = FindWindowEx(hWndDesk, 0, vbNullString, vbNullString)

 
        Dim strText As String
        Dim lngRet As Long
        Do While hWnd <> 0
            strText = String$(100, Chr$(0))
            lngRet = GetClassName(hWnd, strText, 100)

 
            If Left$(strText, lngRet) = «EXCEL7» Then
                GetExcelObjectFromHwnd hWnd
                Exit Sub
            End If

 
            hWnd = FindWindowEx(hWndDesk, hWnd, vbNullString, vbNullString)
            Loop

 
        On Error Resume Next
    End If

 
    Exit Sub

 
MyErrorHandler:
    MsgBox «GetWbkWindows» & vbCrLf & vbCrLf & «Err = » _
        & Err.Number & vbCrLf & «Description: » & Err.Description
End Sub

 
Sub GetExcelObjectFromHwnd(ByVal hWnd As Long)
    On Error GoTo MyErrorHandler

 
    Dim fOk As Boolean

   
    fOk = False

 
    Dim iid As UUID
    Call IIDFromString(StrPtr(IID_IDispatch), iid)

 
    Dim obj As Object
    If AccessibleObjectFromWindow(hWnd, OBJID_NATIVEOM, iid, obj) = 0 Then ‘S_OK
        Dim objApp As Excel.Application

       
        Set objApp = obj.Application

       
        Dim wb As Workbook
        For Each wb In objApp.Workbooks
            With ThisWorkbook.Sheets(«Sheet1»)

               

                ‘Following line of code outputs workbook names only
                ‘Comment out the line and uncommment code between asterisk lines
                ‘to output the workbook name plus worksheet names
                .Cells(.Rows.Count, 1).End(xlUp).Offset(1, 0) = wb.FullName

               

                ‘******************************************************************************
                ‘Dim myWorksheet As Worksheet
                ‘For Each myWorksheet In wb.Worksheets
                ‘    .Cells(.Rows.Count, 1).End(xlUp).Offset(1, 0) = wb.FullName
                ‘    .Cells(.Rows.Count, 1).End(xlUp).Offset(0, 1) = myWorksheet.Name
                ‘Next myWorksheet
                ‘***************************************************************************
            End With

       
            DoEvents
        Next wb
        fOk = True
    End If

 
    Exit Sub

 
MyErrorHandler:
    MsgBox «GetExcelObjectFromHwnd» & vbCrLf & vbCrLf & «Err = » _
        & Err.Number & vbCrLf & «Description: » & Err.Description
End Sub


Regards, OssieMac

Public Declare PtrSafe Function AccessibleObjectFromWindow Lib "oleacc" (ByVal hwnd As LongPtr, ByVal dwId As LongPtr, ByRef riid As Any, ByRef ppvObject As Object) As LongPtr
Public Declare PtrSafe Function FindWindowEx Lib "USER32" Alias "FindWindowExA" (ByVal hWnd1 As LongPtr, ByVal Hwnd2 As LongPtr, ByVal lpsz1 As String, ByVal lpsz2 As String) As LongPtr
Public Declare PtrSafe Function GetWindowThreadProcessId Lib "USER32" (ByVal hwnd As LongPtr, lpdwProcessId As Long) As Long

I feel quite strongly that API declares are rarely used to their full potential in VBA. People too often transliterate these declarations — rewriting C++ code in VBA, rather than translating them into idiomatic VBA. The Alias keyword and ByRef/ByVal modifiers can be used to achieve a higher standard of abstraction similar to what you would expect from other VBA code you write, and renaming variables to avoid Systems Hungarian notation which is not required in VBA can simplify the black box of WinApi calls as well. For example why do I need to know lpszFoo is a long pointer to a zero terminated string when VBA literally defines native strings in that way? The code calling the API does not need to be aware of this low level information, get rid of it. I sometimes even declare the same function twice under different aliases to match the context in which it is called.

So I would modify the declares to something like this:

'Stolen from https://github.com/wine-mirror/wine/blob/08b01d8271fe15c7680a957778d506221d7d94eb/include/winuser.h#L3181-L3195
Private Enum ObjectIdentifier
    OBJID_WINDOW = 0
    OBJID_SYSMENU = -1
    OBJID_TITLEBAR = -2
    OBJID_MENU = -3
    OBJID_CLIENT = -4
    OBJID_VSCROLL = -5
    OBJID_HSCROLL = -6
    OBJID_SIZEGRIP = -7
    OBJID_CARET = -8
    OBJID_CURSOR = -9
    OBJID_ALERT = -10
    OBJID_SOUND = -11
    OBJID_QUERYCLASSNAMEIDX = -12
    OBJID_NATIVEOM = -16     'This is the one you use,  &HFFFFFFF0
End Enum

Private Declare PtrSafe Function AccessibleObjectFromWindow Lib "oleacc" (ByVal hWnd As LongPtr, ByVal ID As ObjectIdentifier, ByRef interfaceRiid As Any, ByRef outInstance As Object) As LongPtr
Private Declare PtrSafe Function FindWindowEx Lib "USER32" Alias "FindWindowExA" (ByVal hWndParent As LongPtr, ByVal hWndChildAfter As LongPtr, ByVal className As String, Optional ByVal windowName As String) As LongPtr
Private Declare PtrSafe Function GetWindowThreadProcessId Lib "USER32" (ByVal hwnd As LongPtr, ByRef outProcessId As Long) As Long
  • Everything Private because these are implementation details and don’t need to be exposed.
  • Introduce an ObjectIdentifier Enum to get rid of magic &HFFFFFFF0 later on.
  • In AccessibleObjectFromWindow, change ByVal dwId As LongPtr to ByVal ID As ObjectIdentifier since dw stands for DWORD which is a 32-bit integer always, just like VBA’s Long (Enum is an alias for Long, so they can be used interchangeably).
  • Explicit ByRef keyword added to the lpdwProcessID because it’s important it doesn’t become ByVal.
  • In fact if we say ByRef we can drop the lp prefix since that’s what ByRef means, and we can also drop dw since that’s what As Long means.
  • Use outBlah for variables which will be assigned by the API functions.
  • It’s fine to use Optional in these functions too if you always pass vbNullString

I know you probably just copy-pasted from vb forums so moving on…


Function ProcIDFromHWnd(ByVal hwnd As Variant) As Variant
    Dim idProc As Variant
    GetWindowThreadProcessId hwnd, idProc
    ProcIDFromHWnd = idProc
End Function

Instead, how about without variant:

Private Function ProcIDFromHWnd(ByVal hwnd As LongPtr) As Long
    If GetWindowThreadProcessId(hwnd, outProcessId:=ProcIDFromHWnd) = 0 Then
        Err.Raise 5, Description:="We didn't get a valid thread ID"
    End If
End Function

Notice you can also use the return value to see if the function succeeded.


Dim hwnd As LongPtr, ChildHwnd As LongPtr, PID As LongPtr

I guess declare these closer to usage would be nice, although I see you use conditional compilation so it could get messy. Should be PID As Long though, again DWORDs are always 32-bit.*

*Oversized variables still generally happen to work — because VBA is managing the memory so it gets allocated/ deallocated properly still, and integral types are little endian (or fill memory in reverse) meaning the first 32 bits of a Long or a LongLong with the same value will be the same.


On Error Resume Next
PID = ProcIDFromHWnd(hwnd)
Map.Add WindowObject.Application, CStr(PID)
On Error GoTo 0

What error are you expecting? Better to remove so you can launch into the debugger when an error occurs.


Do
    hwnd = FindWindowEx(0, hwnd, CStr(ClassList(0)), vbNullString)
    If hwnd = 0 Then Exit Do

    ChildHwnd = hwnd
    For n = 1 To UBound(ClassList)
        ChildHwnd = FindWindowEx(ChildHwnd, 0, CStr(ClassList(n)), vbNullString)
    Next
    '[...]
    DoEvents
Loop

This is kinda dangerous having an infinite loop with DoEvents. What if that allows the Z-Order of windows to change, meaning hwnd = FindWindowEx(0, hwnd, CStr(ClassList(0)), vbNullString) will never return 0 since you keep hopping between windows. Removing the DoEvents would be good although the situation could still crop up where you have a circular reference because another application is misbehaving. Safer would be to keep a track of all hwnds you’ve seen and then exit the loop if one repeats.

Also the process of looping through top level windows with FindWindowEx(0, 0 |prevhwnd, specialClassName,"") has a few too many re-assignments and could be cleaned up.

I would add maybe:

Private Function TryGetNextWindowHwnd(ByVal className As String, ByVal prevHWnd As LongPtr, ByRef outNextHWnd As LongPtr) As Boolean
    outNextHWnd = FindWindowEx(0, prevHWnd, className)
    TryGetNextWindowHwnd = outNextHWnd <> 0
End Function

… which you can use in the loop:

Do While TryGetNextWindowHwnd(TopLevelClassName, parentHWnd, outNextHWnd:=parentHWnd)

For n = 1 To UBound(ClassList)
    ChildHwnd = FindWindowEx(ChildHwnd, 0, CStr(ClassList(n)), vbNullString)
Next

Maybe add a comment saying what this does, drilling down through window class names took me by surprise a little. Also CStr is redundant, and since ClassList(n) only goes from 1 to ubound, maybe ClassList(0) should be passed as its own argument:

Public Function InstanceMap(ByVal TopLevelClassName As String, ParamArray ClassList() As Variant) As Collection

Public Function ExcelInstanceMap() As Collection
    Set ExcelInstanceMap = InstanceMap("XLMAIN", "XLDESK", "EXCEL7")
End Function

Public Function WordInstanceMap() As Collection
    Set WordInstanceMap = InstanceMap("OpusApp", "_WwF", "_WwB", "_WwG")
End Function

Public Function PowerPointInstanceMap() As Collection
    Set PowerPointInstanceMap = InstanceMap("PPTFrameClass", "MDIClient", "mdiClass")
End Function

Really nice simple interface. A shame it isn’t strongly typed as Excel.Application, Word.Application though. Still the iteration variable could be. Also why store PID as the key in a collection at all; since collection keys are not readable, the only reason I can think to use it would be to lookup the instance of the current process’ Application from the collection — but in that case you could just use the default Application object.

Not sure it can be done with GetObject, but this is how you can do it with
the Windows API:

Option Explicit
Public Declare Function GetDesktopWindow Lib «user32» () As Long
Public Declare Function GetWindow Lib «user32» _
(ByVal hwnd As Long, _
ByVal wCmd As Long) As Long
Public Declare Function GetWindowText Lib «user32» _
Alias «GetWindowTextA» _
(ByVal hwnd As Long, _
ByVal lpString As String, _
ByVal cch As Long) As Long
Public Declare Function GetClassName Lib «user32» _
Alias «GetClassNameA» _
(ByVal hwnd As Long, _
ByVal lpClassName As String, _
ByVal nMaxCount As Long) As Long
Public Const GW_HWNDFIRST = 0
Public Const GW_HWNDLAST = 1
Public Const GW_HWNDNEXT = 2
Public Const GW_HWNDPREV = 3
Public Const GW_OWNER = 4
Public Const GW_CHILD = 5

Sub test()

Dim i As Long
Dim collWindows As Collection

Set collWindows = New Collection

FindWindowHwndLike 0, «XLDESK», «», 0, 0, collWindows

If collWindows.Count > 0 Then
For i = 1 To collWindows.Count
MsgBox collWindows(i)
Next
End If

End Sub

Function FindWindowHwndLike(hWndStart As Long, _
ClassName As String, _
WindowTitle As String, _
level As Long, _
lHolder As Long, _
collWindows As Collection) As Long

Dim hwnd As Long
Dim sWindowTitle As String
Dim sClassName As String
Dim r As Long

If level = 0 Then
If hWndStart = 0 Then
hWndStart = GetDesktopWindow()
End If
End If

‘Increase recursion counter
‘—————————
level = level + 1

‘Get first child window
‘———————-
hwnd = GetWindow(hWndStart, GW_CHILD)

Do While hwnd > 0
‘Search children by recursion
‘—————————-
lHolder = FindWindowHwndLike(hwnd, _
ClassName, _
WindowTitle, _
level, _
lHolder, _
collWindows)

‘Get the window text
‘——————-
sWindowTitle = Space$(255)
r = GetWindowText(hwnd, sWindowTitle, 255)
sWindowTitle = Left$(sWindowTitle, r)

‘get the class name
‘——————
sClassName = Space$(255)
r = GetClassName(hwnd, sClassName, 255)
sClassName = Left$(sClassName, r)

If (InStr(1, sWindowTitle, WindowTitle, vbBinaryCompare) > 0 Or _
sWindowTitle = WindowTitle) And _
(sClassName Like ClassName & «*» Or _
sClassName = ClassName) Then
FindWindowHwndLike = hwnd
lHolder = hwnd
collWindows.Add hwnd
End If

‘Get next child window
‘———————
hwnd = GetWindow(hwnd, GW_HWNDNEXT)
Loop

FindWindowHwndLike = lHolder

End Function

RBS

«Xav» <Xaviar.Goulay@uk.standardchartered.com> wrote in message
news:1135424972.276765.137810@g47g2000cwa.googlegroups.com…


> For my application I would like to retrieve all Excel instances
> runnning on a specific machine. Basically I want to use the GetObject
> method and stock it in an array or whatsoever. Does anybody have an
> idea?
> Thanks for helping.
>

INTELLIGENT WORK FORUMS
FOR COMPUTER PROFESSIONALS

Contact US

Thanks. We have received your request and will respond promptly.

Log In

Come Join Us!

Are you a
Computer / IT professional?
Join Tek-Tips Forums!

  • Talk With Other Members
  • Be Notified Of Responses
    To Your Posts
  • Keyword Search
  • One-Click Access To Your
    Favorite Forums
  • Automated Signatures
    On Your Posts
  • Best Of All, It’s Free!

*Tek-Tips’s functionality depends on members receiving e-mail. By joining you are opting in to receive e-mail.

Posting Guidelines

Promoting, selling, recruiting, coursework and thesis posting is forbidden.

Students Click Here

Find All Instances of excel and close

Find All Instances of excel and close

(OP)

21 Nov 05 12:01

Please could someone point me in the right direction, I need to find all open excel documents and close them. If any are not saved, the user must be prompted and also the script must wait for all excel apps to be closed before proceeding with the rest of the code.

This is a vague ‘stab in the dark’ at the code, but to be honest i’m just guessing from stuff i’ve found around the forum. This is my first day on vbScript !! (although I know vba if that’s any help)

Any help much appreciated as usual.

jimlad

«There is more to life than simply increasing its speed.»
-Mahatma Gandhi

Red Flag Submitted

Thank you for helping keep Tek-Tips Forums free from inappropriate posts.
The Tek-Tips staff will check this out and take appropriate action.

Join Tek-Tips® Today!

Join your peers on the Internet’s largest technical computer professional community.
It’s easy to join and it’s free.

Here’s Why Members Love Tek-Tips Forums:

  • Tek-Tips ForumsTalk To Other Members
  • Notification Of Responses To Questions
  • Favorite Forums One Click Access
  • Keyword Search Of All Posts, And More…

Register now while it’s still free!

Already a member? Close this window and log in.

Join Us             Close

If you have an Excel workbook that has hundreds of worksheets, and now you want to get a list of all the worksheet names, you can refer to this article. Here we will share 3 simple methods with you.

Sometimes, you may be required to generate a list of all worksheet names in an Excel workbook. If there are only few sheets, you can just use the Method 1 to list the sheet names manually. However, in the case that the Excel workbook contains a great number of worksheets, you had better use the latter 2 methods, which are much more efficient.

Method 1: Get List Manually

  1. First off, open the specific Excel workbook.
  2. Then, double click on a sheet’s name in sheet list at the bottom.
  3. Next, press “Ctrl + C” to copy the name.Copy Sheet Name
  4. Later, create a text file.
  5. Then, press “Ctrl + V” to paste the sheet name.Paste Sheet Name
  6. Now, in this way, you can copy each sheet’s name to the text file one by one.

Method 2: List with Formula

  1. At the outset, turn to “Formulas” tab and click the “Name Manager” button.
  2. Next, in popup window, click “New”.Name Manager
  3. In the subsequent dialog box, enter “ListSheets” in the “Name” field.
  4. Later, in the “Refers to” field, input the following formula:
=REPLACE(GET.WORKBOOK(1),1,FIND("]",GET.WORKBOOK(1)),"")

Customize New Name

  1. After that, click “OK” and “Close” to save this formula.
  2. Next, create a new worksheet in the current workbook.
  3. Then, enter “1” in Cell A1 and “2” in Cell A2.
  4. Afterwards, select the two cells and drag them down to input 2,3,4,5, etc. in Column A.Enter Sequential Numbers
  5. Later, put the following formula in Cell B1.
=INDEX(ListSheets,A1)

Enter Formula in Cell B1

  1. At once, the first sheet name will be input in Cell B1.
  2. Finally, just copy the formula down until you see the “#REF!” error.Copy Formula Down to List Sheet Names

Method 3: List via Excel VBA

  1. For a start, trigger Excel VBA editor according to “How to Run VBA Code in Your Excel“.
  2. Then, put the following code into a module or project.
Sub ListSheetNamesInNewWorkbook()
    Dim objNewWorkbook As Workbook
    Dim objNewWorksheet As Worksheet

    Set objNewWorkbook = Excel.Application.Workbooks.Add
    Set objNewWorksheet = objNewWorkbook.Sheets(1)

    For i = 1 To ThisWorkbook.Sheets.Count
        objNewWorksheet.Cells(i, 1) = i
        objNewWorksheet.Cells(i, 2) = ThisWorkbook.Sheets(i).Name
    Next i

    With objNewWorksheet
         .Rows(1).Insert
         .Cells(1, 1) = "INDEX"
         .Cells(1, 1).Font.Bold = True
         .Cells(1, 2) = "NAME"
         .Cells(1, 2).Font.Bold = True
         .Columns("A:B").AutoFit
    End With
End Sub

VBA Code - List Sheet Names

  1. Later, press “F5” to run this macro right now.
  2. At once, a new Excel workbook will show up, in which you can see the list of worksheet names of the source Excel workbook.Listed Sheet Names in New Excel Workbook

Comparison

Advantages Disadvantages
Method 1 Easy to operate Too troublesome if there are a lot of worksheets
Method 2 Easy to operate Demands you to type the index first
Method 3 Quick and convenient Users should beware of the external malicious macros
Easy even for VBA newbies

Excel Gets Corrupted

MS Excel is known to crash from time to time, thereby damaging the current files on saving. Therefore, it’s highly recommended to get hold of an external powerful Excel repair tool, such as DataNumen Outlook Repair. It’s because that self-recovery feature in Excel is proven to fail frequently.

Author Introduction:

Shirley Zhang is a data recovery expert in DataNumen, Inc., which is the world leader in data recovery technologies, including sql fix and outlook repair software products. For more information visit www.datanumen.com

1 / 1 / 1

Регистрация: 29.05.2015

Сообщений: 13

1

21.09.2015, 23:48. Показов 5186. Ответов 3


Студворк — интернет-сервис помощи студентам

Добрый день.
Имеется файл Excel, он уже открыт. Как получить на него ссылку, чтобы я мог работать с его листами и ячейками? Во всех примерах, что я находил, сначала запускается Exсel и открыватся необходимый файл:
«excelapp = new Excel.Application();
excelapp.Visible=true;
excelapp.Workbooks.OpenText(…»
У меня же Exсel уже запущен, файл открыт, необходимо только найти на него ссылку, чтобы я мог с ним работать.

Добавлено через 7 часов 5 минут
В дальних уголках интернета я нашел вот такую конструкцию:
«Excel.Application xlApp = (Excel.Application)System.Runtime.InteropServices. Marshal.GetActiveObject(«Excel.Application»);»
Она каким-то образом все таки умудряется получить ссылку на открытый экземпляр екселя. Если отрыты два файла екселя, она получает ссылку на какой-то один из них. Каким образом она выбирает пока непонятно…



0



es_

217 / 216 / 114

Регистрация: 14.01.2013

Сообщений: 459

22.09.2015, 15:28

2

Лучший ответ Сообщение было отмечено юный_падаван как решение

Решение

Ну вот смотри, подсоединился ты к EXCEL процессу..
Зачастую, и так чаще бывает,

Цитата
Сообщение от юный_падаван
Посмотреть сообщение

Если отрыты два файла екселя

— это открыты не два процесса EXCEL, а две книги, и тебе остаётся выбрать нужную.
Как выбрать? Можно по названию книги, ну вот, например, так:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Excel = Microsoft.Office.Interop.Excel;   
using System.Runtime.InteropServices;
 
namespace evij
{
    public partial class Form1 : Form
    {
        public Excel.Application excelApp;          
        public Excel.Range excelCells;
        public Excel.Sheets excelSheets;
        public Excel.Worksheet excelWorkSheet;
        public Excel.Workbooks excelAppWorkbooks;
        public Excel.Workbook excelAppWorkbook;
        public Form1()
        {
            InitializeComponent();
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
                excelApp = (Excel.Application)Marshal.GetActiveObject("Excel.Application");
 
            excelAppWorkbooks = excelApp.Workbooks; // Получаем список открытых книг
            foreach (Excel.Workbook wb in excelAppWorkbooks)
            {
                if(wb.Name=="ИмяКнигиКоторуюТыБудешьПравить.xlsx")
                {
                    excelAppWorkbook = wb;
                    break;
                }
            }
            excelSheets = excelAppWorkbook.Worksheets;                  // Получаем список листов в нашей книге
            excelWorkSheet = (Excel.Worksheet)excelSheets.get_Item(1);  // Берем первый лист
            excelCells = excelWorkSheet.get_Range("A1", "A1");          // Берём ячейку А1
            excelCells.Value2 = "HiCyberForum";                         // Пишем в неё что-нибудь
 
        }
 
    }
}

Если же у тебя действительно два процесса Excel, то нужно смотреть в сторону ID процесса, но интуиция подсказывает мне, что это не твой случай..
И, думаю, вышенаписанный код направит тебя на путь



1



юный_падаван

1 / 1 / 1

Регистрация: 29.05.2015

Сообщений: 13

22.09.2015, 15:48

 [ТС]

3

Спасибо, теперь вкурил. Я был на полпути :-) С помощью команды:

C#
1
Excel.Application xlApp = (Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");

я подцепился в екселю, а вот пролистать все книги, чтобы найти нужную — не догадался.



0



NikitaF

15 / 1 / 2

Регистрация: 27.07.2015

Сообщений: 10

06.10.2016, 11:08

4

Сегодня столкнулся с почти такой же проблемой, только строчка:

C#
1
_xlApp = (Excel.Application)System.Runtime.InteropServices.Marshal.BindToMoniker("Excel.Application");

или

C#
1
_xlApp = (Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");

Запускает новый экземпляр excel (отдельный процесс со своим PID) и возвращает на него ссылку.
Эти строчки работали хорошо на версиях младше 2013, но вот на самой 2013 такая картина. Может быть подскажете как еще можно решить эту задачу?



0



  • #1

Hi, I have been trying to get a reference to Excel applications using oExcelApp = (Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject(«Excel.Application»); in C#. This is so that I can use code to automatically save any open workbooks & close the first Excel application, and then do the same for the next Excel application, etc. However unlike the other Office apps (Word, etc) the reference to Excel seems to stay permanently (until the code is finished) ie I can’t get the reference to move on to the next Excel application. I have tried killing the Excel app after the first iteration, using threads, etc. Any ideas/thoughts to why this may be happenning?

Control Word Wrap

Press Alt+Enter to move to a new row in a cell. Lets you control where the words wrap.

  • #2

Why are there multiple instances of Excel open in the first place?

I’ve worked a little with automating Excel via C# and let’s just say it was a bit quirky.

It took me about a week to write a simple program to create a new worksheet in an existing workbook and list the shapes.:)

Legacy 98055

Guest


  • #3

I wonder if this is a garbage collection issue? Have you forced disposed?

  • #4

Hi Norie & Tom,
Thanks for getting back to me.

Norie — yes it seems quirky compared to other Office apps. The reason is in case someone has more than one Excel app open.

Tom — yes I have tried forcing disposal and releasing the com object, as follows:
oExcelApp.Quit();
System.Runtime.InteropServices.
Marshal.ReleaseComObject(oExcelApp);
GC.GetTotalMemory(false);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.GetTotalMemory(true);

  • #5

Stephen

I wasn’t referring to Excel being quirky, it was C# I meant.:)

I don’t think the behaviour you are seeing is down to Excel itself.

It could be for some other reason — automating any application isn’t always straightforward.

By the way I’m still wondering why you have multiple instances of Excel open.

One thing I’ve found in the past when automating is that if you don’t reference things properly you can find yourself with ‘ghost’ instances.:eek:

  • #6

Tom — yes I have tried forcing disposal and releasing the com object, as follows:
oExcelApp.Quit();
System.Runtime.InteropServices.Marshal.ReleaseComObject(oExcelApp);
GC.GetTotalMemory(false);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.GetTotalMemory(true);

The prescribed cleanup I use is like this:

Rich (BB code):

GC.Collect();                 
GC.WaitForPendingFinalizers();
GC.Collect();                  
GC.WaitForPendingFinalizers();
 
oExcelApp.Quit();
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(oExcelApp);

Note that GC.Collect() and GC.WaitForPendingFinalizers() are called twice.

If you have a reference to any of the Excel workbooks (which you are saving) then additional cleanup will be required, ie Close workbook then call Marshal.FinalReleaseComObject(oExcelWkb) prior to closing the Excel application.

  • #7

Thanks Colin. I have tried as you suggested, but the instance of Excel is still showing in Task manager after all these GC and Quit commands. I also tried process.kill and this removed it, but got the error ‘The RPC server is unavailable’ when it tried to get the reference of the next Excel application. Any further ideas?

  • #8

Hi,

Please can you post the full code? Which version of C# are you using?

I don’t have VS to hand but I might spot something; if I can’t see anything I can have a play later when I get home from work.

Legacy 98055

Guest


  • #9

Excel does not register itself in the ROT. I wonder if this is causing the problem? Workbooks do register and I have found myself using the workbook object as the root object and simply referring to the parent when I need a reference to the application.

  • #10

Thanks Tom and Colin for your replies.

Colin — I have posted my code so far as below. Am using Visual C# 2008 and Visual Studio 2008.

Tom — yes this is something that I have come across before, and may well be the case. I have tried to get it to register the apps in the past ie once the first one is closed to move on to the next one, but wasn’t able to get it to move on. This was mainly because the first Excel app still existed in memory and showed up on task manager even when the application is quit. How would you recommend changing the code below / suggestions to refer to the workbooks in the first Excel application and then referring to workbooks on the next Excel application?

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.ComponentModel;
using Microsoft.Office.Interop.Excel;
using Excel = Microsoft.Office.Interop.Excel;
using System.Windows.Forms;

namespace
Officedocs
{
class Excelproj
{
public static void ExcelClass()
{

Excel.

Application oExcelApp = null;
try
{
//To get reference to the Excel app
oExcelApp = (Excel.Application)System.Runtime
.InteropServices.
Marshal.GetActiveObject(«Excel.Application»);

oExcelApp.DisplayAlerts =

false; //don’t display updates

Process[] processlist = Process.GetProcessesByName(«Excel»); //Shows number of running Excel apps
foreach (Process theprocess in processlist) //foreach Excel app running
{

if (oExcelApp.Workbooks.Count >= 0) //for worbooks in each Excel app
{
foreach (Excel.Workbook wkb in oExcelApp.Application.Workbooks)
{

//Save files using their own names in the specified folder
Object oSaveAsFileExcel1 = wkb.Name;

//Save each workbook
wkb.SaveAs(oSaveAsFileExcel1, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing,
Excel.
XlSaveAsAccessMode.xlNoChange, XlSaveConflictResolution.xlLocalSessionChanges,
true, Type.Missing, Type.Missing, Type.Missing);

wkb.Close(

true, null, null);

//Release the wbk object
Marshal.FinalReleaseComObject(wkb); //Release the Excel wkb object
}

//Close workbooks
oExcelApp.Workbooks.Close();
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();

oExcelApp.Quit();

System.Runtime.InteropServices.

Marshal.ReleaseComObject(oExcelApp);
}
return;

}

catch //(Exception x)
{ }
}
}
}

Понравилась статья? Поделить с друзьями:
  • Girl in the world word
  • Gillette sensor excel чем заменить
  • Get active excel application
  • Gillette sensor excel станок с подставкой
  • Get a word in sideways