{"id":8283,"date":"2012-02-17T07:00:00","date_gmt":"2012-02-17T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2012\/02\/17\/how-do-i-find-out-which-process-has-a-file-open\/"},"modified":"2012-02-17T07:00:00","modified_gmt":"2012-02-17T07:00:00","slug":"how-do-i-find-out-which-process-has-a-file-open","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20120217-00\/?p=8283","title":{"rendered":"How do I find out which process has a file open?"},"content":{"rendered":"<p>\nClassically, there was no way to find out which process has a file open.\nA file object has a reference count, and when the reference count drops\nto zero, the file is closed.\nBut there&#8217;s nobody keeping track of which processes own how many references.\n(And that&#8217;s ignoring the case that the reference is not coming from a\nprocess in the first place; maybe it&#8217;s coming from a kernel driver,\nor maybe it came from a process that no longer exists but whose reference\nis being kept alive by a kernel driver that\n<a HREF=\"http:\/\/msdn.microsoft.com\/library\/ff558679.aspx\">\ncaptured the object reference<\/a>.)\n<\/p>\n<p>\nThis falls into the category of\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2009\/02\/17\/9426787.aspx\">\nnot keeping track of information you don&#8217;t need<\/a>.\nThe file system doesn&#8217;t care who has the reference to the file object.\nIts job is to close the file when the last reference goes away.\n<\/p>\n<p>\nYou do the same thing with your COM object reference counts.\nAll you care about is whether your reference count has reached zero\n(at which point it&#8217;s time to destroy the object).\nIf you later discover an object leak in your process,\nyou don&#8217;t have a magic query\n&#8220;Show me all the people who called\n<code>AddRef<\/code> on my object&#8221;\nbecause you never kept track of all the people who called\n<code>AddRef<\/code> on your object.\nOr even, &#8220;Here&#8217;s an object I want to destroy.\nShow me all the people who called <code>AddRef<\/code> on it\nso I can destroy them\nand get them to call <code>Release<\/code>.&#8221;\n<\/p>\n<p>\nAt least that was the story under the classical model.\n<\/p>\n<p>\nEnter the\n<a HREF=\"http:\/\/msdn.microsoft.com\/library\/cc948910.aspx\">\nRestart Manager<\/a>.\n<\/p>\n<p>\nThe official goal of the Restart Manager is to help make it possible to\nshut down and restart applications which are using a file you want\nto update.\nIn order to do that, it needs to keep track of which processes are\nholding references to which files.\nAnd it&#8217;s that database that is of use here.\n(Why is the kernel keeping track of which processes have a file open?\nBecause it&#8217;s the converse of the principle of not keeping track\nof information you don&#8217;t need:\nNow it needs the information!)\n<\/p>\n<p>\nHere&#8217;s a simple program which takes a file name on the command line\nand shows which processes have the file open.\n<\/p>\n<pre>\n#include &lt;windows.h&gt;\n#include &lt;RestartManager.h&gt;\n#include &lt;stdio.h&gt;\nint __cdecl wmain(int argc, WCHAR **argv)\n{\n DWORD dwSession;\n WCHAR szSessionKey[CCH_RM_SESSION_KEY+1] = { 0 };\n DWORD dwError = RmStartSession(&amp;dwSession, 0, szSessionKey);\n wprintf(L\"RmStartSession returned %d\\n\", dwError);\n if (dwError == ERROR_SUCCESS) {\n   PCWSTR pszFile = argv[1];\n   dwError = RmRegisterResources(dwSession, 1, &amp;pszFile,\n                                 0, NULL, 0, NULL);\n   wprintf(L\"RmRegisterResources(%ls) returned %d\\n\",\n           pszFile, dwError);\n  if (dwError == ERROR_SUCCESS) {\n   DWORD dwReason;\n   UINT i;\n   UINT nProcInfoNeeded;\n   UINT nProcInfo = 10;\n   RM_PROCESS_INFO rgpi[10];\n   dwError = RmGetList(dwSession, &amp;nProcInfoNeeded,\n                       &amp;nProcInfo, rgpi, &amp;dwReason);\n   wprintf(L\"RmGetList returned %d\\n\", dwError);\n   if (dwError == ERROR_SUCCESS) {\n    wprintf(L\"RmGetList returned %d infos (%d needed)\\n\",\n            nProcInfo, nProcInfoNeeded);\n    for (i = 0; i &lt; nProcInfo; i++) {\n     wprintf(L\"%d.ApplicationType = %d\\n\", i,\n                              rgpi[i].ApplicationType);\n     wprintf(L\"%d.strAppName = %ls\\n\", i,\n                              rgpi[i].strAppName);\n     wprintf(L\"%d.Process.dwProcessId = %d\\n\", i,\n                              rgpi[i].Process.dwProcessId);\n     HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,\n                                   FALSE, rgpi[i].Process.dwProcessId);\n     if (hProcess) {\n      FILETIME ftCreate, ftExit, ftKernel, ftUser;\n      if (GetProcessTimes(hProcess, &amp;ftCreate, &amp;ftExit,\n                          &amp;ftKernel, &amp;ftUser) &amp;&amp;\n          CompareFileTime(&amp;rgpi[i].Process.ProcessStartTime,\n                          &amp;ftCreate) == 0) {\n       WCHAR sz[MAX_PATH];\n       DWORD cch = MAX_PATH;\n       if (QueryFullProcessImageNameW(hProcess, 0, sz, &amp;cch) &amp;&amp;\n           cch &lt;= MAX_PATH) {\n        wprintf(L\"  = %ls\\n\", sz);\n       }\n      }\n      CloseHandle(hProcess);\n     }\n    }\n   }\n  }\n  RmEndSession(dwSession);\n }\n return 0;\n}\n<\/pre>\n<p>\nThe first thing we do is call, no wait, even before we call\nthe <code>Rm&shy;Start&shy;Session<\/code> function, we have the line\n<\/p>\n<pre>\n WCHAR szSessionKey[CCH_RM_SESSION_KEY+1] = { 0 };\n<\/pre>\n<p>\nThat one line of code addresses two bugs!\n<\/p>\n<p>\nFirst is a documentation bug.\nThe documentation for the\n<code>Rm&shy;Start&shy;Session<\/code> function doesn&#8217;t specify\nhow large a buffer you need to pass for the session key.\nThe answer is <code>CCH_RM_SESSION_KEY+1<\/code>.\n<\/p>\n<p>\nSecond is a code bug.\nThe\n<code>Rm&shy;&shy;StartSession<\/code> function doesn&#8217;t properly\nnull-terminate the session key, even though the function\nis documented as returning a null-terminated string.\nTo work around this bug, we pre-fill the buffer with null characters\nso that whatever ends gets written will have a null terminator\n(namely, one of the null characters we placed ahead of time).\n<\/p>\n<p>\nOkay, so that&#8217;s out of the way.\nThe basic algorithm is simple:\n<\/p>\n<ol>\n<li>Create a Restart Manager session.\n<li>Add a file resource to the session.\n<li>Ask for a list of all processes affected by that resource.\n<li>Print some information about each process.\n<li>Close the session.\n<\/ol>\n<p>\nWe already mentioned that you create the session by calling\n<code>Rm&shy;Start&shy;Session<\/code>.\nNext, we add a single file resource to the session by\ncalling <code>Rm&shy;Register&shy;Resources<\/code>.\n<\/p>\n<p>\nNow the fun begins.\nGetting the list of affected processes is normally a two-step\naffair.\nFirst, you ask for the number of affected processes\n(by passing <code>0<\/code> as the <code>nProcInfo<\/code>),\nthen allocate some memory and call a second time to get the data.\nBut this is just a sample program, so I&#8217;ve hard-coded a limit\nof ten processes.\nIf more than ten processes are affected, I just give up.\n(You can see this if you ask for all the processes that\nhave open handles to <code>kernel32.dll<\/code>.)\n<\/p>\n<p>\nThe other tricky part is mapping the <code>RM_PROCESS_INFO<\/code>\nto an actual process.\nSince process&nbsp;IDs can be recycled,\nthe\n<code>RM_PROCESS_INFO<\/code> structure identifies a process\nby the combination of the process&nbsp;ID and the process creation time.\nThat combination is unique because two processes cannot have the same\nID at the same time.\nWe open the handle to the process via its ID, then confirm that the\nstart times match.\n(If not, then\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2010\/08\/26\/10054386.aspx#10054750\">\nthe ID refers to a process that exited<\/a>\nduring the\ntime we obtained the list and the time we actually looked at it.)\nAssuming it all matches, we get the image name and print it.\n<\/p>\n<p>\nAnd that&#8217;s all there is to enumerating all the processes that have\na particular file open.\nOf course, a more expressive interface for managing files in use\nis\n<code>IFileIsInUse<\/code>,\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2010\/08\/06\/10046812.aspx\">\nwhich I mentioned some time ago<\/a>.\nThat interface not only tells you the application that has the file open\n(in a friendlier format than just an executable path),\nyou can also use it to switch to the application and even ask it to\nclose the file.\n(Windows&nbsp;7 first tries <code>IFileIsInUse<\/code>,\nand if that fails, then it goes to the Restart Manager.)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Classically, there was no way to find out which process has a file open. A file object has a reference count, and when the reference count drops to zero, the file is closed. But there&#8217;s nobody keeping track of which processes own how many references. (And that&#8217;s ignoring the case that the reference is not [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-8283","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Classically, there was no way to find out which process has a file open. A file object has a reference count, and when the reference count drops to zero, the file is closed. But there&#8217;s nobody keeping track of which processes own how many references. (And that&#8217;s ignoring the case that the reference is not [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/8283","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=8283"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/8283\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=8283"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=8283"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=8283"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}