매크로로 보고서나 다운로드 파일을 저장할 때 "경로를 찾을 수 없습니다" 오류가 나는 경우 대부분은 저장할 폴더가 없어서입니다. 날짜별 폴더나 거래처별 폴더처럼 실행할 때마다 새로 생기는 경로라면 더 그렇죠. 저장 직전에 폴더부터 확인하고, 없으면 만들어 두는 짧은 함수를 하나 공용 모듈에 넣어 두고 씁니다.
Public Function CheckFolder(ByVal f_Spec As String)
Dim fs As Object
Set fs = CreateObject("Scripting.FileSystemObject")
If fs.FolderExists(f_Spec) Then
Set fs = Nothing
Else
fs.CreateFolder f_Spec
Set fs = Nothing
End If
End Function
동작과 사용법
Scripting.FileSystemObject 를 늦은 바인딩(CreateObject)으로 만들기 때문에 참조 설정(Microsoft Scripting Runtime) 없이 바로 쓸 수 있습니다. FolderExists 로 폴더가 있는지 보고, 없으면 CreateFolder 로 만듭니다. 이미 있는 폴더에 CreateFolder 를 부르면 "파일이 이미 있습니다" 오류가 나니, 확인을 먼저 하는 이 순서가 필요합니다.
함수로 선언했지만 반환값은 쓰지 않으니 사실상 Sub처럼 호출합니다.
Dim savePath As String
savePath = ThisWorkbook.Path & "\" & Format(Date, "yyyymmdd")
CheckFolder savePath
ActiveWorkbook.SaveCopyAs savePath & "\보고서.xlsx"
상위 폴더가 없을 때
CreateFolder 는 마지막 한 단계만 만듭니다. D:\자료\2021\07 을 만들려는데 D:\자료\2021 이 아직 없으면 "경로를 찾을 수 없습니다" 오류가 납니다. 위의 CheckFolder 도 마찬가지라, 중간 폴더가 비어 있을 수 있는 경로라면 상위 폴더부터 차례로 만들어 줘야 합니다. 아래는 그 부분을 보완해서 새로 쓴 재귀 버전입니다.
Public Sub CheckFolderDeep(ByVal f_Spec As String)
Dim fs As Object, parentPath As String
Set fs = CreateObject("Scripting.FileSystemObject")
If Not fs.FolderExists(f_Spec) Then
parentPath = fs.GetParentFolderName(f_Spec)
If Len(parentPath) > 0 Then CheckFolderDeep parentPath
fs.CreateFolder f_Spec
End If
Set fs = Nothing
End Sub
GetParentFolderName 으로 한 단계 위 경로를 구해서, 그 폴더가 없으면 먼저 자기 자신을 다시 불러 만들고 내려옵니다. 드라이브 루트까지 올라가면 거기는 이미 있으니 재귀가 멈춥니다.
그 밖에
경로 끝에 역슬래시가 붙어 있어도 FolderExists 는 대체로 잘 판단하지만, 파일 이름까지 붙은 전체 경로를 넘기면 그 파일 이름으로 폴더가 생겨 버립니다. 저장할 파일 경로에서 폴더 부분만 넘기는지 확인하세요. 네트워크 드라이브나 권한이 없는 위치라면 만들기 자체가 실패하니, 중요한 작업이라면 On Error 로 실패를 잡아 사용자에게 알려 주는 편이 낫습니다. VBA 기본 함수 MkDir 과 Dir(경로, vbDirectory) 조합으로도 같은 일을 할 수 있는데, 한글이나 긴 경로에서는 FileSystemObject 쪽이 다루기 편했습니다.
Sub MakeFolder(ByVal p As String)
If Len(Dir(p, vbDirectory)) = 0 Then MkDir p
End Sub
MkDir 로 할 때는 이 정도면 됩니다. 다만 Dir 은 진행 중인 다른 Dir 검색과 상태를 공유합니다. 폴더 안 파일을 Dir 로 하나씩 돌면서 그 안에서 이 함수를 부르면 바깥 루프의 다음 Dir() 이 엉뚱한 결과를 돌려주니, 파일 목록을 도는 코드 안에서는 FileSystemObject 버전을 쓰는 게 안전합니다.
같은 이름의 파일이 있으면
폴더를 만들려는 자리에 확장자 없는 같은 이름의 파일이 있으면 FolderExists 는 False를 돌려주고, 이어서 CreateFolder 가 "파일이 이미 있습니다" 오류를 냅니다. 폴더 이름이 거래처명처럼 데이터에서 오는 경우 드물게 겪는 일이라, 오류 메시지만 보고 "폴더가 있는데 왜?" 하고 헷갈리기 쉽습니다. MkDir 버전은 반대로 Dir 이 파일 이름도 돌려주기 때문에 폴더를 안 만들고 넘어가 버리고, 그다음 저장 단계에서 실패합니다.
상대 경로와 OneDrive 경로
f_Spec 에 C:\ 나 \\서버 로 시작하지 않는 상대 경로를 넘기면 통합 문서가 있는 폴더가 아니라 현재 작업 폴더(CurDir) 기준으로 만들어집니다. 파일 열기 대화상자를 한 번 거치면 현재 폴더가 바뀌기도 해서, 실행할 때마다 다른 곳에 폴더가 생기는 것처럼 보일 수 있습니다. 위 예제처럼 ThisWorkbook.Path 를 앞에 붙여 항상 전체 경로로 넘기세요.
통합 문서가 OneDrive나 SharePoint 동기화 폴더에 있으면 ThisWorkbook.Path 가 로컬 경로가 아니라 https:// 로 시작하는 주소를 돌려줄 때가 있습니다. 이 값으로는 폴더를 확인하거나 만들 수 없으니, 그 환경에서 쓰는 매크로라면 저장 위치를 Environ("USERPROFILE") 아래처럼 로컬 경로로 따로 지정하는 쪽이 덜 꼬입니다.
CheckFolder 를 만든 결과까지 확인하고 싶다면 Function 선언을 살려 As Boolean 을 붙이고, 두 갈래에 있는 Set fs = Nothing 을 End If 뒤 한 곳으로 모은 다음 그 바로 앞에서 CheckFolder = fs.FolderExists(f_Spec) 를 돌려주게 바꾸면 호출하는 쪽에서 If Not CheckFolder(savePath) Then Exit Sub 처럼 쓸 수 있습니다.