파일이 다른 프로세스에서 사용 중이라 삭제·저장이 안 될 때, 잡고 있는 프로그램 찾는 법
파일이 "다른 프로세스에서 사용 중"이라 삭제나 저장이 안 되면, 그 파일을 열고 있는 프로그램을 닫으면 됩니다. 어느 프로그램인지 모를 때가 문제인데, 윈도우에 묻는 방법이 있습니다. 아래 스크립트에 파일 경로를 넣으면 프로그램 이름과 번호(PID)가 나옵니다. 이 글에서는 임시 폴더에서 일부러 파일을 잡아 두고 재현한 결과를 보여 드립니다.
프로그램을 닫기 전에 저장하지 않은 작업이 있는지 먼저 확인하세요. 강제로 끝내면 저장 안 된 내용은 사라질 수 있습니다.
어떤 오류인가
이 PC에서 파일 하나를 다른 프로그램이 열어 둔 상태로 만든 뒤, PowerShell로 지워 보고 내용을 덧붙여 보았습니다.
--- 1. 삭제 시도
'D:\작업\demo\보고서.txt' 파일은 다른 프로세스에서 사용 중이므로 프로세스에서 액세스할 수 없습니다.
--- 2. 쓰기 시도
'D:\작업\demo\보고서.txt' 파일은 다른 프로세스에서 사용 중이므로 프로세스에서 액세스할 수 없습니다.
삭제도 저장(쓰기)도 같은 문구로 거부됩니다. 경로는 글에 맞게 바꿔 적었습니다. 파일 탐색기에서 지울 때 "사용 중"이라는 안내 창이 뜨는 경우도 원인은 같습니다. 어떤 프로그램이 그 파일을 열면서 "나만 쓰겠다"고 윈도우에 신청해 둔 상태입니다.
먼저 해 볼 것
복잡한 방법 전에 아래를 확인하세요.
- 그 파일을 열었던 프로그램(엑셀, 워드, 한글, 이미지 뷰어, 동영상 재생기 등)을 모두 닫습니다. 창이 보이지 않아도 작업 표시줄 오른쪽 아래의 숨겨진 아이콘 안에 남아 있을 수 있습니다.
- 파일 탐색기에서 해당 폴더를 연 창도 닫아 보세요. 미리 보기 창이 파일을 읽고 있을 수 있습니다.
- 파일이 클라우드 동기화 폴더(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)도 있으니, 끝내기 전에 저장할 것이 없는지 확인하세요.
설치형 프로그램을 쓰기 어렵다면 위 스크립트로도 충분합니다.
정리
- 파일을 열었을 만한 프로그램과 탐색기 창을 먼저 닫습니다.
- 안 풀리면
find_locker.ps1에 파일 경로를 넣어 잡고 있는 프로그램을 찾습니다. - 그 프로그램을 저장한 뒤 정상적으로 닫습니다. 작업 관리자의 작업 끝내기는 마지막 수단입니다.
- 그래도 목록이 비어 있으면 재부팅합니다.
디스크 용량이 부족해서 임시 파일을 지우다 이 오류를 만났다면 윈도우 C 드라이브가 갑자기 꽉 찼을 때, 무엇이 차지하는지 찾는 순서 (지우기 전에 읽기만 하는 방법) 글도 함께 보세요.