SimpleIsBest.NET

유경상의 닷넷 블로그

네트워크 드라이브(공유 폴더) 작업하기

by 블로그쥔장 | 작성일자: 2005-06-07 오전 11:35:00
이 글은 오래된 전에 작성된 글입니다. 따라서 최신 버전의 기술에 알맞지 않거나 오류를 유발할 수 있습니다. 저자는 이 글에 대한 질문을 받지 않을 것입니다. 하지만 이 글이 리뉴얼 되면 이 글에 대한 질문을 하거나 토론을 할 수도 있습니다.
시스템을 개발하다 보면 종종 네트워크 드라이브를 액세스해야 할 때가 있습니다. 대개의 프로젝트에서 파일 업로드 기능을 구현할 때 로컬 하드 디스크나 SAN(Storage Area Network) 디스크와 같이 로컬 디스크와 동등한 저장소에 업로드 된 파일을 저장하기도 하지만 많은 경우, 별도의 스토리지 서버에 저장하는 경우가 많죠. 이때 스토리지가 NAS(Network Area Strorage)와 같은 윈도우의 파일 공유 메카니즘을 이용하는 경우, 프로그램은 네트워크 연결을 수행해야 합니다. 지난주에 제가 참여하는 프로젝트에서 요구된 프로그램적으로 이 네트워크 연결을 수행하는 방법에 대해 주절거려 볼까 합니다. 대부분 잘 아시겠지만... 혹 아직 모르시는 분들을 위해...

Working with Shared Folder...

폴더를 공유함으로써 네트워킹을 한다는 것이 초창기 NOS(Network Operating System)의 모토이자 지금도 많이 사용되는 통신 기법 중 하나이다. 폴더를 공유하거나 네트워크 드라이브를 만드는 것에 관련된 닷넷 환경의 API는 무엇인가?

필자가 아는 한 관련된 닷넷 네임스페이스나 클래스는 없다(있다면 알려주기 바람 -_-). 이상 블로그 끝~~ 텨텨텨

WNet API

대부분 그러하지만 닷넷에 관련 클래스가 없다면, 해결법은 Interop 밖에 없다. Win32 API에는 틀림없이 관련 API 가 있을 것이다. 하지만... MSDN을 막연히 디비볼려면 거의 죽음에 가깝다고 볼 수 있다. 짭밥과 통밥을 이용하여 MSDN을 노려보면 Networking & Directory Service 라는 최상위 레벨의 항목을 찾을 수 있고 그 하부에 Network Management 항목을 찾을 수 있다. 이 항목 아래에는 NetShell, SNMP, Network Management 등의 항목들이 있고 그 중 Windows Networking (WNet) 이란 녀석을 찾을 수 있다. 요 녀석이 Windows 3.1 시절부터 지금까지 내려오는 윈도우의 네트워크 드라이브에 관련된 WIN32 API 집합이 되겠다.

WNet API들은 파일 서버(파일 공유를 제공하는 컴퓨터, PC나 노트북일 수도 있겠지?)에서 제공하는 공유 폴더에 접근하기 위한 네트워크 연결에 대한 다양한 API들을 제공한다. 예를 들면, 네트워크 드라이브 연결시 나타나는 다음과 같은 다이얼로그 박스를 나타내는 API, 이러한 UI 없이 네트워크 연결을 수행해주는 API, 연결되어 있는 네트워크 자원을 열거해 주는 API 등등 유용한 것이 많다.

WNetUseConnection() function

우리가 필요한 것은 ASP.NET 코드나 기타 프로그램에서 사용자의 관여 없이 미리 지정된 서버의 공유 폴더에 접근하고 싶은 것이다. 이때 사용할 수 있는 API는 WNetAddConnection, WNetAddConnection2, WNetAddConnection3, WNetUseConnection 등이 있다. WNetAddConnection 류의 API는 반드시 D:, E: 류의 드라이브명 (API 문서에는 Local Name 이라 부르고 있다)을 명시해 주어야 하며 WNetUseConnection은 드라이브 명을 자동으로 설정하는 옵션을 가질 수 있다는 점이 상이할 뿐 비슷한 기능을 가진다.

고로 이 글에서는 WNetUserConnection을 사용하는 방법만 설명할 것이다. 이쯤되면 Interop을 좀 해본 독자라면 바로 구글에 가서 WNetUseConnection을 호출하는 닷넷 Interop 코드를 검색할 것이다. 그렇다. 이 Win32 API를 닷넷에서 호출할 수 있도록 P-Invoke(Platform Invoke) 코드만 제공하면 땡 인 것이다.

WNetUserConnection을 닷넷에서 호출할 수 있다면, 이 함수를 통해 파일 서버의 공유 폴더를 연결하고, 해당 공유 폴더에 단순한 파일 연산(읽기, 쓰기, 복사, 삭제 등등)을 수행하면 된다. 일단 연결되고 나면, \\ServerName\FolderName\File.ext 류의 UNC 이름을 직접 파일 경로로 줄 수도 있으며, 네트워크 드라이브 이름을 주었다면 Z:\\File.ext 의 파일 시스템 경로를 주어서 액세스할 수도 있다.

P-Invoke 코드 작성

WIN32 API가 있으면 뭐하나? WIN32 API를 전혀 모르는 사람(대부분이 여기에 해당할 것이다)이라면 그림의 떡인데... 하지만 닷넷은 P-Invoke란게 있지 않은가? 이를 통해 닷넷에서 WIN32 API를 호출하는 것은 어렵지 않다 !

이제 WNetUseConnection 을 닷넷에서 호출하기 위한 P-Invoke 관련 코드를 작성해 보자. 필요한 것은 WNetUseConnection 함수에 대한 P-Invoke 선언(extern 선언)이 필요하며, 이 함수가 매개변수로 취하는 구조체에 대한 닷넷 선언이 필요하다.

NETRESOURCE Structure

구글에서 Interop 코드를 찾았다면 상황 종료이지만, 직접 P-Invoke 코드를 만들어 보는 것도... 나쁘지 않을 듯하다. 앞서 언급한 API들은 NETRESOURCE 라는 구조체(structure)를 매개변수로 사용한다. 다른 정수형 매개변수나 문자열을 어케 해보겠는데 이렇게 구조체가 떡 나타나면 대략 골치가 아프기 마련이다. 다행이도 NETRESOURCE는 그다지 복잡한 구조체가 아니여서 어렵지 않게 선언을 할 수 있다. 다음은 필자의 NETRESOURCE 구조체 선언이다. (당근 C# 코드이다. VB.NET 개발자라면 요것을 수정해 보든가 구글에서 검색을 해보길...)

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
public struct NETRESOURCE
{
    public uint dwScope;
    public uint dwType;
    public uint dwDisplayType;
    public uint dwUsage;
    public string lpLocalName;
    public string lpRemoteName;
    public string lpComment;
    public string lpProvider;
}

ANSI, UNICODE 모두 사용 가능하도록 CharSet을 Auto로 주었으며, DWORD (double word, 즉 32비트 부호 없는 정수)를 위해 uint 타입을 사용했다는 점을 빼면 너무 심플해서 설명하기 난감할 정도다.

WNetUseConnection method Declaration

WNetUseConnection API에 대한 닷넷 메쏘드 선언은 extern 키워드와 DllImportAttribute 를 이용해 선언하면 된다.

[DllImport("mpr.dll", CharSet=CharSet.Auto)]
public static extern int WNetUseConnection(
            IntPtr hwndOwner,
            [MarshalAs(UnmanagedType.Struct)] ref NETRESOURCE lpNetResource,
            string lpPassword,
            string lpUserID,
            uint dwFlags,
            StringBuilder lpAccessName,
            ref int lpBufferSize,
            out uint lpResult);

각 매개변수에 필요한 값들은 MSDN을 참고하기 바란다. 이것까지 설명해 달라는 것은 대략 도둑넘 심보다.

Caller Code Example

이제 WNetUseConnection 메쏘드를 호출하는 코드만 작성하면 끝이다.

int capacity = 64;
uint resultFlags = 0;
uint flags = 0;
System.Text.StringBuilder sb = new System.Text.StringBuilder(capacity);
NETRESOURCE ns = new NETRESOURCE();
ns.dwType = 1;           // 공유 디스크
ns.lpLocalName = null;   // 로컬 드라이브 지정하지 않음
ns.lpRemoteName = @"\\fileserver\sharefoldername";
ns.lpProvider = null;

int result = WNetUseConnection(IntPtr.Zero, ref ns, "password", "userid", flags,
                                sb, ref capacity, out resultFlags);

위 코드는 공유 폴더에 대한 네트워크 연결을 만들며 로컬 드라이브 명을 명시하지 않았다. result 변수 값이 0 이면 오류가 없는 것이며, 0 이 아닌 값은 오류가 발생했음을 알리는 오류 코드이다. 공유 폴더 경로에 오류가 있는 경우, 1203 오류 코드가, 사용자/암호가 일치 하지 않다면 1326 오류 코드가 발생한다.

명시적으로 로컬 드라이브를 지정하고 싶다면 NETRESOURCE 구조체의 lpLocalName 필드에 Z:, Y: 등의 값을 설정할 수 있다. 이 경우, 이미 네트워크 드라이브 사용 중이라면 오류 코드 85 를 반환할 것이다. 만약, 사용 가능한 로컬 드라이브 (Z, Y, X, W, 등의 순서) 이름의 선택을 시스템에게 맞기고 싶다면 flags 값에 0x80 을 주면 시스템이 적절한 로컬 드라이브 이름을 선택해 줄 것이다. 그리고 그 드라이브 이름은 StringBuilder 를 통해 값이 반환된다. flags 가 0x80 이 아닌 값이 사용되면 StringBuilder는 일반적으로 공유 폴더의 UNC 이름을 반환한다. 따라서 capacity 값은 공유 폴더의 경로를 담을 수 있도록 충분히 주어야 한다. 그렇지 않으면 오류 코드 234를 반환할 것이다.

더 상세한 WNetUseConnection 함수에 대한 정보는 MSDN을 참고하기 바란다. 이만큼 설명한 것도 오버한 것이 아닌가 싶다... -_-

짧은 포스트는 없는가?

이번 포스트만은 짧게 할려고 했는데... 왤케 주저리 주저리 길게 써 대는지... 실은 네트워크 연결을 끊는 API 인 WNetCancelConnection 에 대해서도 몇마디 쓸려다가 꾹~~ 참았다. 함수가 그다지 어렵지 않을 뿐더러 P-Invoke 선언 역시 간단한 매개변수로서 선언이 그다지 어렵지 않기 때문이기도 하지만 길어지는 포스트의 압박에...



Comments (read-only)
#re: 네트워크 드라이브(공유 폴더) 작업하기 / 임용섭 / 2007-01-05 오후 2:17:00
공유폴더 접근에 PInvoke를 이용하고 싶었지만, 구체적인 방법을 몰라 헤메던 차였습니다. 좋은 포스트 정말 감사합니다.
#re: 네트워크 드라이브(공유 폴더) 작업하기 / 이철현 / 2007-01-09 오후 8:14:00
좋은 포스트입니다. 감사합니다.

혹시 result 값이 5인경우는 어떤경우인지 알수 있을까요?
#re: 네트워크 드라이브(공유 폴더) 작업하기 / 블로그쥔장 / 2007-01-09 오후 10:05:00
결과 값이 5 이면 "액세스가 거부되었습니다." 라는 오류입니다.
공유 폴더에 권한이 없을 가능성이 높습니다.
#re: 네트워크 드라이브(공유 폴더) 작업하기 / 김창민 / 2007-03-07 오후 5:22:00
좋은 내용 항상 잘보고 있습니다. 헌데 위내용 따라 하다가 보면
result 가 1200 이 나옵니다. 저쪽(192.168.0.11) PC 는 WIN2003 이 설치 되 있구요
암호랑 ID 도 맞게 넣었습니다.

IPLocalName = "H:";
IPRemoteName = @"\\192.168.0.11\Xess_1"

이렇게 했거든요.. OS 실행 창에서 \\192.168.0.11\Xess_1 일케 넣고 실행 하면
잘 접속이 됩니다. 어떤경우 인가요 ㅠㅠ

1200 오류 찾아 보니까 "지정한 장치 이름이 올바르지 않습니다. "
라고 나오는데 뭔지 알수가 없네요..
#re: 네트워크 드라이브(공유 폴더) 작업하기 / 김수영 / 2007-04-20 오후 12:30:00
좋은글 잘 읽었습니다.
저도 원격지에 대한 공유 폴더의 인증처리 자동화를 이용한 파일 카피가 필요해서 정말 삽질(?)을 많이 했었는데
조금 읽찍 이 내용을 보았다면...빨리 해결 할 수 있었을 것 같은데....(아~ 그 동안의 시간....)

위에 분처럼 1200 와 같이 오류 발생원인을 보니 CharSet.Auto 로 하니 그러더군요...
(클라이언트는 Win xp , 서버는 win 2003)

그래서 CharSet.Ansi로 변경하니 잘 되더라구요 ^^
#re: 네트워크 드라이브(공유 폴더) 작업하기 / 정아 / 2007-06-14 오후 3:57:00
오류가 87번인 경우는 무엇인지 알수있을까요??
간호 1237?이라는 번호도 나오는데...
어떻게 오류를 찾아야 할지를 몰라서여..^^;;
#re: 네트워크 드라이브(공유 폴더) 작업하기 / 정아 / 2007-06-14 오후 4:20:00
1219번..에러는...
어떤에러인지 알 수 있을까요?
#re: 네트워크 드라이브(공유 폴더) 작업하기 / 블로그쥔장 / 2007-06-15 오전 1:08:00
MSDN을 뒤지거나 SDK에서 관련 헤더를 뒤지면 답이 나온답니다.

87- 매개변수가 틀립니다.
1237 - 작업을 마치지 못했습니다. 다시 시도하십시오.
1219 - 동일한 사용자가 둘 이상의 사용자 이름으로 서버 또는 공유 리소스에 다중 연결할 수 없습니다. 서버나 공유 리소스에 대한 이전 연결을 모두 끊고 다시 시도하십시오.
#re: 네트워크 드라이브(공유 폴더) 작업하기 / 쌍둥아빠 / 2008-01-25 오후 1:20:00
정말 도움이 많이 되었습니다.
가상디렉토리를 통해 가장을통해 코드말고 서버설정으로 해볼려구 삽질하고 안돼고 있었는데
이런방법이 있었군요 정말 대단합니다
#re: 네트워크 드라이브(공유 폴더) 작업하기 / 조승태 / 2008-03-05 오후 4:39:00
좋은 내용, 감사합니다.
잘 보았습니다. ^^
#re: 네트워크 드라이브(공유 폴더) 작업하기 / 조승태 / 2008-03-20 오전 9:52:00
쥔장님의 블로그를 보면 볼수록,
api 를 모르면 c#도 큰 힘을 못 실겠다는 생각이 드네요.
아... api.. ㅠㅜ
#re: 로컬경로..? / 조주일 / 2008-03-20 오전 10:30:00
만약 A 컴이 자신이 가진 네트웍 드라이브가 대상 PC 에서 어떤 경로인지 아는 방법이 있을까요 ㅠㅠ
#re: 네트워크 드라이브(공유 폴더) 작업하기 / 이현재 / 2008-10-23 오후 12:05:00
실제로는 네트워크 드라이브 연결 계정의 권한으로 파일 억세스을 할 수 있어야 하지만 로그인 사용자의 권한으로 억세스되는 현상이 발생하고 있습니다.
이를 Impersonation해서 접속을 하면 된다고 누군가에게 들었는데...혹시 방법을 아시는지요...?
MSDN에 있다더라란 풍문이 있지만 도데체 찾지를 못하겠네요...ㅡㅡ;
#re: 네트워크 드라이브(공유 폴더) 작업하기 / 블로그쥔장 / 2008-10-27 오전 10:43:00
ASP.NET에서 공유 폴더를 액세스하는 방법에 대해서
제 블로그 포스트 시리즈를 읽어 보십시요.

http://www.simpleisbest.net/archive/2005/12/02/309.aspx

도움이 되실겁니다.
#re: 네트워크 드라이브(공유 폴더) 작업하기 / 손님 / 2008-10-31 오후 2:39:00
좋은 정보 정말 감사합니다.
그런데. 제 컴에서 127.0.0.1 로 접속할 경우에, 1219, 1231 등의 오류가 나면서 접속이 안되는 경우가 있던데,
원인이 뭔지 통 모르겠습니다.
어떤경우에는 접속이 되고, 어떤 경우에는 접속이 안되는데,,근본적인 해결책이 있을까요?
#re: 네트워크 드라이브(공유 폴더) 작업하기 / 블로그쥔장 / 2008-11-10 오후 12:13:00
글쎄요... 1219 오류는 동일한 사용자가 둘 이상의 사용자 계정을 통해 동일
공유 폴더에 접근하려고 할 때 발생하는 오류입니다. 기존 연결이 존재하는 상황에서
다른 계정으로 연결하고자 할 때 주로 발생하지요. 기존 연결을 먼저 제거해야 합니다.
1231 오류는 네트워크 위치를 찾을 수 없을 때 발생합니다.
네트워크 경로에 사용된 컴퓨터 이름을 찾을 수 없거나 공유 폴더 이름이 잘못된 경우,
혹은 파일 서버가 다운되어 있거나 방화벽으로 인해서도 발생하기도 합니다.

근본적인 해결책은 원인에 따라 다릅니다. 정확한 원인만 알아낸다면
근본적인 해결책도 찾을 수 있겠지요. 하지만 발생할 수 있는 모든 상황에 대한
하나의 근본적인 해결책이란 없습니다. 따라서 다양한 상황에 대해서 충분히 고려하고
코드를 작성하셔야 합니다.
#re: 네트워크 드라이브(공유 폴더) 작업하기 / 손님 / 2009-04-27 오후 6:11:00
웹서버에서 파일서버로 공유폴더를 연결해서 잘 쓰던중 연결이 안되서 문의드립니다.
전에도 한번 연결이 안되서 드라이브명을 바꾸고 리부팅했더니 잘 사용이 됐습니다.
이번에도 드라이브명을 바꾸고 재부팅을 아무리 해봐도 연결이 안되네요.
그래서 디버깅 하니 errorcode = 85 더라구요.
커멘드창에서 net use 해서 드라이브명을 보면 사용된 드라이브명이 없는데 계속 에러코드가 85로 나오네요.
WNetCancelConnection 를 하게되면 2250 not connected 라고 나오고.

어디를 어떻게 봐야알지도 막막하네요.
#re: 네트워크 드라이브(공유 폴더) 작업하기 / 짜집기 / 2009-08-20 오후 2:28:00
도움 많이 되고 있습니다. 자바 개발자라 닷넷 하기가 쉽지많은 않네요.
다름이 아니라 공유 폴더에 접근 뿐만 아니라 그 폴더의 공유폴더퍼미션에 설정된 user 나 그룹정보 그리고 보안에 설정된 user나 group 정보를 알 수 있는 방법이 있나요?