Dsg951
Dsg951

Reputation: 23

Copy a file over network to a destination outside domain

I want to copy a file from computer A(with account myAccount@mydomain) to computer B(userB@computerB) over the network using c#. I tried the standard

File.Copy(source,destination)

and tried starting a cmd process (from computer A) and call the copy method

System.Diagnostics.Process process = new System.Diagnostics.Process();
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
startInfo.UseShellExecute = false;
startInfo.Domain = "computerB"; //ofcourse it wont work since its outside the local domain of A
startInfo.FileName = "cmd.exe";
startInfo.Arguments = @"/C COPY \\computerA\Path\File1.txt \\computerB\Path$ ";
process.StartInfo = startInfo;
process.Start();
process.WaitForExit();
//It will exit the user name or password is incorrect

I tried also to use PSexec to impersonate computerB :

 System.Diagnostics.Process process = new System.Diagnostics.Process();
 System.Diagnostics.ProcessStartInfo startInfo = new 
 System.Diagnostics.ProcessStartInfo();
 startInfo.UseShellExecute = false;
 startInfo.FileName = "cmd.exe";
 startInfo.Arguments = @"psexec \\computerB -u computerB\userB -p userBPassword cmd /c COPY \\computerA\Path\File1.txt \\computerB\Path$";
 process.StartInfo = startInfo;
 process.Start();
 process.WaitForExit();
//it will exit that the source file is unknown

To sum it up,computer A is able to see the source(itself) but not the destination(since computer B has only authorized local user). computer B is able to see the destination(itself) but not the source(since computer A is outside its domain and its not shared over the network).

Is there a workaround for this issue?

Upvotes: 0

Views: 514

Answers (1)

Corey
Corey

Reputation: 16574

It sounds like this is a fairly simple authentication problem of the type that pops up whenever the current user doesn't have rights on a share, both in a domain and out. The problem also arises when running under the system user and trying to access shares on other devices.

The solution is to open an authenticated connection to the target device using the WNetUseConnection API, perform your file operations then close the connection with a call to WNetCancelConnection2.

Here's some code I have used in the past for this:

class ConnectSMB : IDisposable
{
    public string URI { get; private set; }
    public bool Connected { get; private set; } = false;

    public ConnectSMB(string uri, string username, string encPassword)
    {
        string pass = StringEncryption.Decode(encPassword);
        string connResult = Native.ConnectToRemote(uri, username, pass);
        if (connResult != null)
            throw new Exception(connResult);
            
        URI = uri;
        Connected = true;
    }

    public void Dispose()
    {
        Close();
    }

    public void Close()
    {
        if (Connected)
        {
            Native.DisconnectRemote(URI);
            URI = null;
            Connected = false;
        }
    }
}

public class Native
{
    #region Consts
    const int RESOURCETYPE_DISK = 1;
    const int CONNECT_UPDATE_PROFILE = 0x00000001;
    #endregion

    #region Errors
    public enum ENetUseError
    {
        NoError = 0,
        AccessDenied = 5,
        AlreadyAssigned = 85,
        BadDevice = 1200,
        BadNetName = 67,
        BadProvider = 1204,
        Cancelled = 1223,
        ExtendedError = 1208,
        InvalidAddress = 487,
        InvalidParameter = 87,
        InvalidPassword = 1216,
        MoreData = 234,
        NoMoreItems = 259,
        NoNetOrBadPath = 1203,
        NoNetwork = 1222,
        BadProfile = 1206,
        CannotOpenProfile = 1205,
        DeviceInUse = 2404,
        NotConnected = 2250,
        OpenFiles = 2401
    }
    #endregion

    #region API methods
    [DllImport("Mpr.dll")]
    private static extern ENetUseError WNetUseConnection(
        IntPtr hwndOwner,
        NETRESOURCE lpNetResource,
        string lpPassword,
        string lpUserID,
        int dwFlags,
        string lpAccessName,
        string lpBufferSize,
        string lpResult
    );

    [DllImport("Mpr.dll")]
    private static extern ENetUseError WNetCancelConnection2(
        string lpName,
        int dwFlags,
        bool fForce
    );

    [StructLayout(LayoutKind.Sequential)]
    private class NETRESOURCE
    {
        // Not used
        public int dwScope = 0;
        // Resource Type - disk or printer
        public int dwType = RESOURCETYPE_DISK;
        // Not used
        public int dwDisplayType = 0;
        // Not used
        public int dwUsage = 0;
        // Local Name - name of local device (optional, not used here)
        public string lpLocalName = "";
        // Remote Name - full path to remote share
        public string lpRemoteName = "";
        // Not used
        public string lpComment = "";
        // Not used
        public string lpProvider = "";
    }
    #endregion

    public static string ConnectToRemote(string remoteUNC, string username, string password)
    {
        NETRESOURCE nr = new NETRESOURCE
        {
            lpRemoteName = remoteUNC
        };

        ENetUseError ret = WNetUseConnection(IntPtr.Zero, nr, password, username, 0, null, null, null);
        if (ret == ENetUseError.NoError) return null;
        return ret.ToString();
    }

    public static string DisconnectRemote(string remoteUNC)
    {
        ENetUseError ret = WNetCancelConnection2(remoteUNC, CONNECT_UPDATE_PROFILE, false);
        if (ret == ENetUseError.NoError) return null;
        return ret.ToString();
    }
}

Create an instance of the ConnectSMB class before you do your remote file operations, then dispose (or close) it when you're done with the connection.

using (var smb = new ConnectSMB(@"\\computerB\Path", "userB", "userBPassword"))
{
    File.Copy(source, destination);
}

Upvotes: 1

Related Questions