일단 한 대 쳐 보고

파일이 다른 프로세스에서 사용 중이라 삭제·저장이 안 될 때, 잡고 있는 프로그램 찾는 법

파일이 "다른 프로세스에서 사용 중"이라 삭제나 저장이 안 되면, 그 파일을 열고 있는 프로그램을 닫으면 됩니다. 어느 프로그램인지 모를 때가 문제인데, 윈도우에 묻는 방법이 있습니다. 아래 스크립트에 파일 경로를 넣으면 프로그램 이름과 번호(PID)가 나옵니다. 이 글에서는 임시 폴더에서 일부러 파일을 잡아 두고 재현한 결과를 보여 드립니다.

프로그램을 닫기 전에 저장하지 않은 작업이 있는지 먼저 확인하세요. 강제로 끝내면 저장 안 된 내용은 사라질 수 있습니다.

어떤 오류인가

이 PC에서 파일 하나를 다른 프로그램이 열어 둔 상태로 만든 뒤, PowerShell로 지워 보고 내용을 덧붙여 보았습니다.

--- 1. 삭제 시도
'D:\작업\demo\보고서.txt' 파일은 다른 프로세스에서 사용 중이므로 프로세스에서 액세스할 수 없습니다.
--- 2. 쓰기 시도
'D:\작업\demo\보고서.txt' 파일은 다른 프로세스에서 사용 중이므로 프로세스에서 액세스할 수 없습니다.

삭제도 저장(쓰기)도 같은 문구로 거부됩니다. 경로는 글에 맞게 바꿔 적었습니다. 파일 탐색기에서 지울 때 "사용 중"이라는 안내 창이 뜨는 경우도 원인은 같습니다. 어떤 프로그램이 그 파일을 열면서 "나만 쓰겠다"고 윈도우에 신청해 둔 상태입니다.

먼저 해 볼 것

복잡한 방법 전에 아래를 확인하세요.

  1. 그 파일을 열었던 프로그램(엑셀, 워드, 한글, 이미지 뷰어, 동영상 재생기 등)을 모두 닫습니다. 창이 보이지 않아도 작업 표시줄 오른쪽 아래의 숨겨진 아이콘 안에 남아 있을 수 있습니다.
  2. 파일 탐색기에서 해당 폴더를 연 창도 닫아 보세요. 미리 보기 창이 파일을 읽고 있을 수 있습니다.
  3. 파일이 클라우드 동기화 폴더(OneDrive 등)에 있다면 동기화가 끝나기를 잠시 기다립니다.

이렇게 해도 안 풀리거나 어느 프로그램인지 짐작이 안 가면 아래로 진행합니다.

잡고 있는 프로그램 찾기

설치할 프로그램은 없습니다. 윈도우에는 설치 프로그램이 "이 파일을 쓰는 프로그램을 닫아야 합니다"라고 알려 줄 때 쓰는 기능(Restart Manager)이 있고, 이 스크립트가 그 기능에 파일 이름을 물어봅니다. 읽기만 하며, 파일도 프로그램도 건드리지 않습니다.

메모장에 아래 내용을 모두 붙여 넣고 find_locker.ps1이라는 이름으로 저장합니다. 저장할 때 파일 형식을 "모든 파일"로 두고 이름 끝이 .txt가 되지 않게 하세요.

# Read-only. Asks Windows Restart Manager which programs hold the file. Argument: file path
param([Parameter(Mandatory)][string]$Path)
Add-Type -TypeDefinition @"
using System; using System.Runtime.InteropServices; using System.Collections.Generic; using System.Diagnostics;
public static class Locker {
  [StructLayout(LayoutKind.Sequential)] struct FT { public uint lo; public uint hi; }
  [StructLayout(LayoutKind.Sequential)] struct UP { public int pid; public FT start; }
  [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] struct PI {
    public UP proc; [MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)] public string app;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=64)] public string svc;
    public int type; public uint status; public uint sess; [MarshalAs(UnmanagedType.Bool)] public bool restartable; }
  [DllImport("rstrtmgr.dll", CharSet=CharSet.Unicode)] static extern int RmStartSession(out uint h, int f, string key);
  [DllImport("rstrtmgr.dll")] static extern int RmEndSession(uint h);
  [DllImport("rstrtmgr.dll", CharSet=CharSet.Unicode)] static extern int RmRegisterResources(uint h, uint nf, string[] files, uint na, UP[] a, uint ns, string[] s);
  [DllImport("rstrtmgr.dll")] static extern int RmGetList(uint h, out uint need, ref uint have, [In,Out] PI[] info, out uint reasons);
  public static List<string> Who(string path) {
    var r = new List<string>(); uint h; var key = Guid.NewGuid().ToString();
    if (RmStartSession(out h, 0, key) != 0) throw new Exception("RmStartSession failed");
    try {
      RmRegisterResources(h, 1, new[]{path}, 0, null, 0, null);
      uint need = 0, have = 0, reasons;
      int rc = RmGetList(h, out need, ref have, null, out reasons);
      if (rc == 234 && need > 0) {
        var info = new PI[need]; have = need;
        rc = RmGetList(h, out need, ref have, info, out reasons);
        for (int i = 0; i < have; i++) {
          string exe = "";
          try { exe = Process.GetProcessById(info[i].proc.pid).ProcessName + ".exe"; } catch {}
          r.Add(info[i].proc.pid + "\t" + exe + "\t" + info[i].app);
        }
      } else if (rc != 0) { throw new Exception("RmGetList error " + rc); }
    } finally { RmEndSession(h); }
    return r;
  }
}
"@
$found = [Locker]::Who((Resolve-Path -LiteralPath $Path).Path)
if ($found.Count -eq 0) { "No program is holding this file." }
else { "PID`tProcess`tProgram"; $found }

PowerShell을 열고 스크립트를 저장한 폴더에서 아래처럼 실행합니다. 파일 경로에는 문제가 되는 파일의 전체 경로를 큰따옴표로 감싸 넣습니다. 탐색기에서 파일을 선택하고 Shift 키를 누른 채 오른쪽 클릭하면 "경로로 복사" 메뉴가 나옵니다. 메뉴가 다르면 "윈도우 파일 경로 복사"로 검색해 보세요.

powershell -ExecutionPolicy Bypass -File .\find_locker.ps1 "D:\작업\demo\보고서.txt"

-ExecutionPolicy Bypass가 왜 필요한지는 아래 "스크립트가 실행되지 않을 때"에서 설명합니다. 이 PC에서 재현한 결과입니다.

--- 3. 잡고 있는 프로그램 찾기
PID	Process	Program
25316	powershell.exe	Windows PowerShell
(실제로 잡고 있는 프로세스 PID = 25316)

표의 각 줄이 파일을 잡고 있는 프로그램입니다. Process가 프로그램 이름, PID가 윈도우가 붙인 번호입니다. 마지막 줄의 괄호는 재현할 때 제가 미리 알고 있던 정답입니다. 번호가 일치하므로 스크립트가 맞게 찾은 것입니다. 이 재현에서는 파일을 열어 두기만 하는 PowerShell 창을 잡고 있는 프로그램으로 썼기 때문에 powershell.exe가 나왔습니다. 엑셀이 잡고 있다면 그 자리에 엑셀의 프로세스 이름이 나올 것으로 예상합니다.

그 프로그램이 눈에 보이는 창이면 저장할 것이 있는지 확인하고 정상적으로 닫으세요. 창이 안 보이면 Ctrl + Shift + Esc로 작업 관리자를 열어 같은 이름의 항목을 찾습니다. 작업 끝내기를 누르면 저장하지 않은 내용이 사라질 수 있습니다. 이름이 낯선 시스템 프로세스나 보안 프로그램이면 끝내지 마세요.

프로그램이 닫힌 뒤에 같은 명령을 다시 실행하면 아무것도 안 나옵니다.

--- 4. 프로그램을 끝낸 뒤 다시 찾기
No program is holding this file.
삭제 성공: True

그 뒤에 삭제가 성공했습니다. 이 삭제는 재현용 임시 폴더의 시험 파일이었습니다. 여러분의 파일을 지우기 전에는 휴지통으로 가는지 확인하세요. PowerShell의 Remove-Item으로 지우면 휴지통을 거치지 않고 바로 사라집니다. 탐색기에서 지우는 편이 안전합니다.

스크립트가 실행되지 않을 때

윈도우 기본 설정에서는 저장한 스크립트를 그냥 실행하면 막히는 경우가 많습니다. 이 PC의 설정이 그랬고, -ExecutionPolicy Bypass 없이 실행하면 이런 오류가 나왔습니다(화면 언어 설정에 따라 한국어로 나올 수도 있습니다).

File ...\find_locker.ps1 cannot be loaded because running scripts is disabled on this system. For more information, see about_Execution_Policies at https:/go.microsoft.com/fwlink/?LinkID=135170.

-ExecutionPolicy Bypass는 그 PowerShell 창 한 번에만 적용됩니다. 시스템 설정이나 레지스트리를 바꾸지 않습니다. 설명은 about_Execution_Policies에 있습니다. 회사 PC에서 보안 정책으로 막아 두었다면 이 방법으로도 안 될 수 있으니 그때는 관리자에게 문의하세요.

목록이 비어 있을 때

위 스크립트가 "No program is holding this file."이라고 해도 파일이 안 지워질 수 있습니다. 그럴 때는 다음을 해 보세요.

PowerToys가 있다면

Microsoft PowerToys를 쓰고 있다면 같은 일을 하는 도구인 File Locksmith가 들어 있습니다. PowerToys 설정에서 File Locksmith를 켜고, 탐색기에서 파일을 오른쪽 클릭해 "Unlock with File Locksmith"를 고르면 그 파일을 쓰는 프로세스가 나온다고 Microsoft 문서가 안내합니다(한국어 메뉴 이름은 다를 수 있습니다). 이 PC의 PowerToys에서는 명령줄 도구(PowerToys.FileLocksmith.CLI.exe)를 --json 옵션으로 실행해 위 스크립트와 같은 프로세스 번호가 나오는 것을 확인했습니다. 같은 도구에 프로세스를 강제로 끝내는 기능(--kill)도 있으니, 끝내기 전에 저장할 것이 없는지 확인하세요.

설치형 프로그램을 쓰기 어렵다면 위 스크립트로도 충분합니다.

정리

  1. 파일을 열었을 만한 프로그램과 탐색기 창을 먼저 닫습니다.
  2. 안 풀리면 find_locker.ps1에 파일 경로를 넣어 잡고 있는 프로그램을 찾습니다.
  3. 그 프로그램을 저장한 뒤 정상적으로 닫습니다. 작업 관리자의 작업 끝내기는 마지막 수단입니다.
  4. 그래도 목록이 비어 있으면 재부팅합니다.

디스크 용량이 부족해서 임시 파일을 지우다 이 오류를 만났다면 윈도우 C 드라이브가 갑자기 꽉 찼을 때, 무엇이 차지하는지 찾는 순서 (지우기 전에 읽기만 하는 방법) 글도 함께 보세요.