Using Python to close an ArcMap map document down

This article isn’t particularly mainstream but at the recent Esri UK user conference I was asked the following question:

How do I close down an open ArcMap map document through a Python script? If I have a number of map documents open I just want a particular map document to close and leave all of the other ones open….

“Hmmmmm – good question. Email me and I will see what I can think of…”

Well the customer emailed earlier this week asking the same question. How am I going to solve this?

Normally I might have a look in the excellent ArcGIS for Desktop help for ArcPy but there was nothing there as this is really not an ArcPy problem.

An internet search found that there were a number of others who had asked the same question but did not really receive a satisfactory reply.

For example:

Use the os.system(“TASKKILL /F /IM ArcMap.exe”)

Which was suggested in this thread https://geonet.esri.com/thread/58161

Or use the Python library psutil to get a list of processes and if the name of a process (Arcmap.exe) is found then stop it using the terminate() method which was suggested on this thread https://geonet.esri.com/thread/80474

There are countless others asking pretty much the same sort of thing (not necessarily to do with a map document) on http://gis.stackexchange.com

As you can imagine both options will close all open instances of ArcMap.

But what is you wanted to be a little more choosy about which instance of ArcMap you wanted to close down? What about if you wanted to close ArcMap down based upon the name of its open map document?

 

Using the psutil Python module

The answer to this is to use the Python module psutil. This module is used to obtain information from running processes – basically anything you have running in Task Manager can be obtained using this module, and a whole lot more.

It is not part of Python’s Standard Library which means you will need to download it and install it onto your machine which you want to run the script on. Thankfully it is incredibly easy to do this as it is part of the Python Package Index (PyPI) repository and modules found here can be installed easily as long as you have an internet connection.

Let’s go and install the psutil

1: Open up a command line prompt

2: At the command prompt, Change Directory (CD) into your Python \Scripts directory, for example:

cd C:\Python27\ArcGISx6410.3\Scripts

Inside this folder are a number of python utility scripts – the one we want to use is pip.exe as this will install the named module within the PyPI repository.

3: At the command prompt type in:

pip.exe install psutil

and press <return>

This will start to install psutil into your Python \Site-Packages folder. As it installs you will see messages along the lines of:


Downloading psutil-4.3.0-cp27-none-win_amd64.whl (169kB)

100% |################################| 172kB 1.9MB/s

Installing collected packages: psutil

Successfully installed psutil-4.3.0



And that is all you have to do to install it! You can now import the module into your script and take advantage of its functionality.

Information about psutil can be found here:

https://pypi.python.org/pypi/psutil

It is well documented and the nice thing about such an unfamiliar (to me) module is that it contains many ‘easy to follow’ samples. If only all Python modules were as well documented….

 

Let’s consider this problem….

I have a number of ArcMap map documents open and part of my workflow is to close a particular map document (Editing.mxd) once I have made some changes to using ArcPy functionality.

Inspecting the Task Manager shows that a number of ArcMap applications are open and each application has its own process ID (View menu > Select Columns and tick PID (Process Identifier) )

01_TaskManager

But which one is the Editing.mxd process? Task Manager can not provide that level of information.

This is where the psutil module comes in handy. This can be used to close down the Editing.mxd map document.

Let’s write some code to do this:

1: Import psutil and get a list of process IDs

mxdName = "Editing.mxd"
import psutil
pidList = psutil.pids()

2: Process the PID list to obtain the corresponding operating system process and get its name

for pidID in pidList:
  process = psutil.Process(pidID)
  pidName = process.name()

3: Find the ArcMap processes and get their memory maps.

  if (pidName == "ArcMap.exe"):
    tMemMaps = process.memory_maps()

The memory_maps() method retrieves information about the processes (ArcMap.exe) as a Python list of tuples.

The information contained in the list of tuples can be inspected using a for .. in loop and one of the items contained within it is the name (and location) of the current ArcMap object which is being processed, for example,  Editing.mxd.

02_MamMaps

4: Parse each memory map, obtain path information and see if the desired mxd is present

      for eachMemMap in tMemMaps:
        mxdPresent = eachMemMap[0].find(mxdName)

The find() method belongs on the string object and returns the index number at which the value contained in mxdName (i.e. Editing.mxd) is found.

5: If the index number is greater than -1 kill the process

        if mxdPresent > -1:
          process.kill()   # Close the desired ArcMap
          break

If the string is not found then a value of -1 is returned from the find() method. If the number is greater than -1 then the map document name is the one we want to close, so let’s kill the process. The break statement is there as it is assumed there is only one instance of the map document called Editing.mxd open so there is no point processing any other process’ memory maps.

The finished script is displayed below:

mxdName = "Editing.mxd"
import psutil
pidList = psutil.pids()

for pidID in pidList:
    process = psutil.Process(pidID)
    pidName = process.name()
    if (pidName == "ArcMap.exe"):   # Process only the
                                    # ArcMap.exe
        #Get the process memory map
        tMemMaps = process.memory_maps() # A list of
                                         # tuples
        for eachMemMap in tMemMaps:
            mxdPresent = eachMemMap[0].find(mxdName)
            if mxdPresent > -1:
                process.kill()      # Close the desired
                                    # ArcMap process
                break
print(mxdName + " has been closed down")

It is available in GitHub at:

https://github.com/epjmorris/ArcPySnippets/tree/master/CloseArcMapScript

 

Let’s go Pro!

Saying that though, this script will not work with ArcGIS Pro – it’s a bit of a Misfire.

Yes – there is an ArcGISPro.exe process which can be seen in Task Manager and the process does have a memory map but within the memory map there is no reference to the currently opened map project (.aprx file).

This isn’t the end of the world though as you can only ever have one instance of ArcGIS Pro running. If you try and launch another ArcGIS Pro project while one is already open then this is the message which will be displayed:

03_InstanceOfPro

In which case you can use the solution as suggested in one of the GeoNet posts, with a very slight amendment:

import os
os.system(“TASKKILL /F /IM ArcGISPro.exe”)

And there you have it! The Python module psutil is a pretty useful module if you want to see what is associated with a particular process. I am thinking of this module as the Python equivalent of Sysinternals very handy “Process Explorer” application.

I think I have only really scratched the surface of this module and it is one I suspect I will use more in the future.

Useful links
Python Standard Library
https://docs.python.org/2.7/library/

Python Package Index (PyPI) repository
https://pypi.python.org/pypi

psutil module homepage
https://pypi.python.org/pypi/psutil/4.3.0

Sysinternals Process Explorer
https://technet.microsoft.com/en-us/sysinternals/processexplorer.aspx