I'm attempting to use low level Windows API's (specifically FindFirstFileW / fileapi.h) in a UWP app for the first time.
I have proof of concept code running successfully in a .Net console app, and now want to try it in a UWP app (personal hobby project). The import statement uses SetLastError = true:
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr FindFirstFileW(string lpFileName, out WIN32_FIND_DATAW lpFindFileData);
The UWP code compiles fine, and runs without throwing any error, but FindFirstFileW(lpFileName, out findData) returns INVALID_HANDLE_VALUE, whereas the console app returns file system data.
I am using the same folder path as the target in both applications.
https://learn.microsoft.com/en-us/uwp/win32-and-com/win32-apis seems to indicate that FindFirstFileW is available to be called.
My UWP app targets v 1803 (10.0; build 17134), min version Creators Update (10.0; build 15063).
Settings -> Privacy -> File system is on.
I have not yet added <rescap:Capability Name="broadFileSystemAccess" /> as described in https://learn.microsoft.com/en-us/windows/uwp/files/file-access-permissions#accessing-additional-locations because I get a warning:
The element 'Capabilities' in namespace
'http://schemas.microsoft.com/appx/manifest/foundation/windows10' has
invalid child element 'Capability' in namespace
'http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities'.
Happy to provide more info.
Update - example project that replicates error:
Full NativeDirectoryScanner.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
namespace FileAPITest
{
public static class FILETIMEExtensions
{
public static DateTime ToDateTime(this System.Runtime.InteropServices.ComTypes.FILETIME filetime)
{
long highBits = filetime.dwHighDateTime;
highBits = highBits << 32;
return DateTime.FromFileTimeUtc(highBits + (long)filetime.dwLowDateTime);
}
}
public class NativeDirectoryScanner
{
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr FindFirstFileW(string lpFileName, out WIN32_FIND_DATAW lpFindFileData);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
public static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATAW lpFindFileData);
[DllImport("kernel32.dll")]
public static extern bool FindClose(IntPtr hFindFile);
[DllImport("kernel32.dll")]
static extern uint GetLastError();
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WIN32_FIND_DATAW
{
public FileAttributes dwFileAttributes;
internal System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
internal System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
internal System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
public int nFileSizeHigh;
public int nFileSizeLow;
public int dwReserved0;
public int dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string cAlternateFileName;
}
public class Info
{
public bool IsDirectory;
public string Path;
public DateTime ModifiedDate;
public DateTime CreatedDate;
}
public static List<Info> RecursiveScan2(string directory)
{
IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
WIN32_FIND_DATAW findData;
IntPtr findHandle = INVALID_HANDLE_VALUE;
//FolderInfo folderInfo;
string lpFileName = string.Empty;
//var info = new List<FolderInfo>();
var list = new List<Info>();
try
{
//lpFileName = directory + #"\*";
lpFileName = directory + "*";
list.Add(new Info()
{
CreatedDate = DateTime.Now,
ModifiedDate = DateTime.Now,
IsDirectory = false,
Path = string.Format("RecursiveScan2.lpFileName (80): {0}", lpFileName)
});
findHandle = FindFirstFileW(lpFileName, out findData);
list.Add(new Info()
{
CreatedDate = DateTime.Now,
ModifiedDate = DateTime.Now,
IsDirectory = false,
Path = string.Format("RecursiveScan2.GetLastError: {0}", GetLastError().ToString())
});
if (findHandle != INVALID_HANDLE_VALUE)
{
do
{
if (findData.cFileName == "." || findData.cFileName == "..") continue;
string fullpath = directory + (directory.EndsWith("\\") ? "" : "\\") + findData.cFileName;
bool isDir = false;
if ((findData.dwFileAttributes & FileAttributes.Directory) != 0)
{
isDir = true;
list.AddRange(RecursiveScan2(fullpath));
}
list.Add(new Info()
{
CreatedDate = findData.ftCreationTime.ToDateTime(),
ModifiedDate = findData.ftLastWriteTime.ToDateTime(),
IsDirectory = isDir,
Path = fullpath
});
}
while (FindNextFile(findHandle, out findData));
}
}
catch (Exception ex)
{
list.Add(new Info()
{
CreatedDate = DateTime.Now,
ModifiedDate = DateTime.Now,
IsDirectory = false,
Path = string.Format("RecursiveScan2.lpFileName (131): {0}", lpFileName)
});
list.Add(new Info()
{
CreatedDate = DateTime.Now,
ModifiedDate = DateTime.Now,
IsDirectory = false,
Path = string.Format("RecursiveScan2.Error: {0}", ex.Message)
});
}
finally
{
if (findHandle != INVALID_HANDLE_VALUE) FindClose(findHandle);
}
return list;
}
}
}
===========================================
Xaml fragment:
<RelativePanel>
<Button x:Name="btnGo" Content="Go" Margin="5" Tapped="BtnGo_Tapped" />
<TextBox x:Name="txtInput" Margin="5" RelativePanel.RightOf="btnGo" RelativePanel.AlignRightWithPanel="True" Text="C:/" />
<TextBlock x:Name="txtoutput" Margin="5,0,5,5" RelativePanel.Below="btnGo" RelativePanel.AlignLeftWithPanel="True" RelativePanel.AlignRightWithPanel="True" RelativePanel.AlignBottomWithPanel="True" />
</RelativePanel>
===========================================
xaml.cs
private void BtnGo_Tapped(object sender, TappedRoutedEventArgs e)
{
StringBuilder sb = new StringBuilder();
try
{
List<Info> list = RecursiveScan2(txtInput.Text);
for(int i = 0; i < list.Count; i++)
{
sb.AppendFormat("Item: {2} - {1}{0}", Environment.NewLine, list[i].Path, i);
}
}
catch(Exception ex)
{
sb.AppendFormat("{0}Error: {1}{0}Stacktrace: {2}", Environment.NewLine, ex.Message, ex.StackTrace);
}
txtoutput.Text = sb.ToString();
}
I tried the code, the code returns the same result as you mentioned. The document of FindFirstFileW mentions this API could be used in UWP apps but it will still be limited by the UWP apps as the UWP are running in the sandbox.
If you are trying to find a file and its handle, there is another way that you could do that. You could get a brokered file HANDLE from a StorageFile with the IStorageItemHandleAccess interface or from a StorageFolder with the IStorageFolderHandleAccess interface. After you've got the handle, you should be able to use the handle in some win32 APIs to do what you want.
Besides, if you don't mind using the desktop bridge, you could do it in the desktop apps, and then package it together with the UWP app. Use the UWP app to launch the desktop app which calls the win32 APIs to do the work.
I am creating a Shared folder on a remote computer/server and have this working but am unable to find if it is possible to change the share settings. I would like to be able to turn off Allow caching of share and turn on Access-based Enumeration but am unable to find anything on this on here and through searching google. Does anybody know if this is possible with C#?
The code i am using to create the share is:
public static void CreateRemoteShare(string servername, string FolderPath, string ShareName, string Description)
{
try
{
string scope = string.Format("\\\\{0}\\root\\cimv2", servername);
ManagementScope ms = new ManagementScope(scope);
ManagementClass managementClass = new ManagementClass("Win32_Share");
managementClass.Scope = ms;
ManagementBaseObject inParams = managementClass.GetMethodParameters("Create");
ManagementBaseObject outParams;
inParams["Description"] = Description;
inParams["Name"] = ShareName;
inParams["Path"] = FolderPath;
inParams["Type"] = 0x0;
NTAccount everyoneAccount = new NTAccount(null, "EVERYONE");
SecurityIdentifier sid = (SecurityIdentifier)everyoneAccount.Translate(typeof(SecurityIdentifier));
byte[] sidArray = new byte[sid.BinaryLength];
sid.GetBinaryForm(sidArray, 0);
ManagementObject everyone = new ManagementClass("Win32_Trustee");
everyone["Domain"] = null;
everyone["Name"] = "EVERYONE";
everyone["SID"] = sidArray;
ManagementObject dacl = new ManagementClass("Win32_Ace");
dacl["AccessMask"] = 2032127;
dacl["AceFlags"] = 3;
dacl["AceType"] = 0;
dacl["Trustee"] = everyone;
ManagementObject securityDescriptor = new ManagementClass("Win32_SecurityDescriptor");
securityDescriptor["ControlFlags"] = 4; //SE_DACL_PRESENT
securityDescriptor["DACL"] = new object[] { dacl };
inParams["Access"] = securityDescriptor;
outParams = managementClass.InvokeMethod("Create", inParams, null);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
If anybody knows of any way to do this i would be eternaly greatfull.
After looking in to this a bit more i found a site that contained a way to do it in PowerShell and from the site i added this to my C# code. The site is: ABE Powershell
The resulting code extracted is as follows:
public enum Share_Type : uint
{
STYPE_DISKTREE = 0x00000000, // Disk Drive
STYPE_PRINTQ = 0x00000001, // Print Queue
STYPE_DEVICE = 0x00000002, // Communications Device
STYPE_IPC = 0x00000003, // InterProcess Communications
STYPE_SPECIAL = 0x80000000, // Special share types (C$, ADMIN$, IPC$, etc)
STYPE_TEMPORARY = 0x40000000 // Temporary share
}
public enum Share_ReturnValue : int
{
NERR_Success = 0,
ERROR_ACCESS_DENIED = 5,
ERROR_NOT_ENOUGH_MEMORY = 8,
ERROR_INVALID_PARAMETER = 87,
ERROR_INVALID_LEVEL = 124, // unimplemented level for info
ERROR_MORE_DATA = 234,
NERR_BufTooSmall = 2123, // The API return buffer is too small.
NERR_NetNameNotFound = 2310 // This shared resource does not exist.
}
[System.Flags]
public enum Shi1005_flags
{
SHI1005_FLAGS_DFS = 0x0001, // Part of a DFS tree (Cannot be set)
SHI1005_FLAGS_DFS_ROOT = 0x0002, // Root of a DFS tree (Cannot be set)
SHI1005_FLAGS_RESTRICT_EXCLUSIVE_OPENS = 0x0100, // Disallow Exclusive file open
SHI1005_FLAGS_FORCE_SHARED_DELETE = 0x0200, // Open files can be force deleted
SHI1005_FLAGS_ALLOW_NAMESPACE_CACHING = 0x0400, // Clients can cache the namespace
SHI1005_FLAGS_ACCESS_BASED_DIRECTORY_ENUM = 0x0800, // Only directories for which a user has FILE_LIST_DIRECTORY will be listed
SHI1005_FLAGS_FORCE_LEVELII_OPLOCK = 0x1000, // Prevents exclusive caching
SHI1005_FLAGS_ENABLE_HASH = 0x2000, // Used for server side support for peer caching
SHI1005_FLAGS_ENABLE_CA = 0X4000 // Used for Clustered shares
}
public static class NetApi32
{
// ********** Structures **********
// SHARE_INFO_502
[StructLayout(LayoutKind.Sequential)]
public struct SHARE_INFO_502
{
[MarshalAs(UnmanagedType.LPWStr)]
public string shi502_netname;
public uint shi502_type;
[MarshalAs(UnmanagedType.LPWStr)]
public string shi502_remark;
public Int32 shi502_permissions;
public Int32 shi502_max_uses;
public Int32 shi502_current_uses;
[MarshalAs(UnmanagedType.LPWStr)]
public string shi502_path;
public IntPtr shi502_passwd;
public Int32 shi502_reserved;
public IntPtr shi502_security_descriptor;
}
// SHARE_INFO_1005
[StructLayout(LayoutKind.Sequential)]
public struct SHARE_INFO_1005
{
public Int32 Shi1005_flags;
}
private class unmanaged
{
//NetShareGetInfo
[DllImport("Netapi32.dll", SetLastError = true)]
internal static extern int NetShareGetInfo(
[MarshalAs(UnmanagedType.LPWStr)] string serverName,
[MarshalAs(UnmanagedType.LPWStr)] string netName,
Int32 level,
ref IntPtr bufPtr
);
[DllImport("Netapi32.dll", SetLastError = true)]
public extern static Int32 NetShareSetInfo(
[MarshalAs(UnmanagedType.LPWStr)] string servername,
[MarshalAs(UnmanagedType.LPWStr)] string netname, Int32 level, IntPtr bufptr, out Int32 parm_err);
}
// ***** Functions *****
public static SHARE_INFO_502 NetShareGetInfo_502(string ServerName, string ShareName)
{
Int32 level = 502;
IntPtr lShareInfo = IntPtr.Zero;
SHARE_INFO_502 shi502_Info = new SHARE_INFO_502();
Int32 result = unmanaged.NetShareGetInfo(ServerName, ShareName, level, ref lShareInfo);
if ((Share_ReturnValue)result == Share_ReturnValue.NERR_Success)
{
shi502_Info = (SHARE_INFO_502)Marshal.PtrToStructure(lShareInfo, typeof(SHARE_INFO_502));
}
else
{
throw new Exception("Unable to get 502 structure. Function returned: " + (Share_ReturnValue)result);
}
return shi502_Info;
}
public static SHARE_INFO_1005 NetShareGetInfo_1005(string ServerName, string ShareName)
{
Int32 level = 1005;
IntPtr lShareInfo = IntPtr.Zero;
SHARE_INFO_1005 shi1005_Info = new SHARE_INFO_1005();
Int32 result = unmanaged.NetShareGetInfo(ServerName, ShareName, level, ref lShareInfo);
if ((Share_ReturnValue)result == Share_ReturnValue.NERR_Success)
{
shi1005_Info = (SHARE_INFO_1005)Marshal.PtrToStructure(lShareInfo, typeof(SHARE_INFO_1005));
}
else
{
throw new Exception("Unable to get 1005 structure. Function returned: " + (Share_ReturnValue)result);
}
return shi1005_Info;
}
public static int NetShareSetInfo_1005(string ServerName, string ShareName, SHARE_INFO_1005 shi1005_Info) // Int32 Shi1005_flags
{
Int32 level = 1005;
Int32 err;
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(shi1005_Info));
Marshal.StructureToPtr(shi1005_Info, ptr, false);
var result = unmanaged.NetShareSetInfo(ServerName, ShareName, level, ptr, out err);
return result;
}
}
static void Main(string[] args)
{
abeTest();
}
public static void abeTest()
{
NetApi32.SHARE_INFO_1005 test = new NetApi32.SHARE_INFO_1005();
test.Shi1005_flags = 2048;
NetApi32.NetShareSetInfo_1005("FileStore2", "TestShare2$", test);
}
By combining both this and the above code you are able to create a share and set the access based enumeration.
This question already has answers here:
Accessing a Shared File (UNC) From a Remote, Non-Trusted Domain With Credentials
(9 answers)
Closed 5 years ago.
I have a windows forms app running on a machine that is not on a domain, that needs to be able to move a file from the local filesystem to a UNC path. I have a username and password for that path. I was wondering is there any way to do this directly with out execing the net.exe command?
Ideally I wouldn't have to map a drive.
You can use WNetAddConnection to accomplish this. You will have to pInvoke. the code below worked for me after I set up the pInvoke declarations. The second block of code (below) contains the pInvoke declarations -- just stick it inside of a class.
public static void CopyFile(string from, string shareName, string username, string password)
{
NETRESOURCE nr = new NETRESOURCE();
nr.dwType = ResourceType.RESOURCETYPE_DISK;
nr.lpLocalName = null;
nr.lpRemoteName = shareName;
nr.lpProvider = null;
int result = WNetAddConnection2(nr, password, username, 0);
System.IO.File.Copy(from, System.IO.Path.Combine(shareName, System.IO.Path.GetFileName(from)));
}
You will need to paste the following supporting code into a class (taken from pInvoke.Net). Make sure to add a using statment to your code:
using System.Runtime.InteropServices
[DllImport("Mpr.dll", EntryPoint = "WNetAddConnection2", CallingConvention = CallingConvention.Winapi)]
private static extern int WNetAddConnection2(NETRESOURCE lpNetResource, string lpPassword,
string lpUsername, System.UInt32 dwFlags);
[StructLayout(LayoutKind.Sequential)]
private class NETRESOURCE
{
public ResourceScope dwScope = 0;
public ResourceType dwType = 0;
public ResourceDisplayType dwDisplayType = 0;
public ResourceUsage dwUsage = 0;
public string lpLocalName = null;
public string lpRemoteName = null;
public string lpComment = null;
public string lpProvider = null;
};
public enum ResourceScope
{
RESOURCE_CONNECTED = 1,
RESOURCE_GLOBALNET,
RESOURCE_REMEMBERED,
RESOURCE_RECENT,
RESOURCE_CONTEXT
};
public enum ResourceType
{
RESOURCETYPE_ANY,
RESOURCETYPE_DISK,
RESOURCETYPE_PRINT,
RESOURCETYPE_RESERVED
};
public enum ResourceUsage
{
RESOURCEUSAGE_CONNECTABLE = 0x00000001,
RESOURCEUSAGE_CONTAINER = 0x00000002,
RESOURCEUSAGE_NOLOCALDEVICE = 0x00000004,
RESOURCEUSAGE_SIBLING = 0x00000008,
RESOURCEUSAGE_ATTACHED = 0x00000010,
RESOURCEUSAGE_ALL = (RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED),
};
public enum ResourceDisplayType
{
RESOURCEDISPLAYTYPE_GENERIC,
RESOURCEDISPLAYTYPE_DOMAIN,
RESOURCEDISPLAYTYPE_SERVER,
RESOURCEDISPLAYTYPE_SHARE,
RESOURCEDISPLAYTYPE_FILE,
RESOURCEDISPLAYTYPE_GROUP,
RESOURCEDISPLAYTYPE_NETWORK,
RESOURCEDISPLAYTYPE_ROOT,
RESOURCEDISPLAYTYPE_SHAREADMIN,
RESOURCEDISPLAYTYPE_DIRECTORY,
RESOURCEDISPLAYTYPE_TREE,
RESOURCEDISPLAYTYPE_NDSCONTAINER
};
The accepted answer on this question here seems like it would be worth looking into; it suggests using the Win32 API function WNetUseConnection.
From MSDN:
The WNetUseConnection function makes a
connection to a network resource. The
function can redirect a local device
to a network resource.
Which seems to accomplish what you're looking for, with no mention of net.exe. Does this help?
We've run into an interesting situation that needs solving, and my searches have turned up nill. I therefore appeal to the SO community for help.
The issue is this: we have a need to programmatically access a shared file that is not in our domain, and is not within a trusted external domain via remote file sharing / UNC. Naturally, we need to supply credentials to the remote machine.
Typically, one solves this problem in one of two ways:
Map the file share as a drive and supply the credentials at that time. This is typically done using the NET USE command or the Win32 functions that duplicate NET USE.
Access the file with a UNC path as if the remote computer were on the domain and ensure that the account under which the program runs is duplicated (including password) on the remote machine as a local user. Basically leverage the fact that Windows will automatically supply the current user's credentials when the user attempts to access a shared file.
Don't use remote file sharing. Use FTP (or some other means) to transfer the file, work on it locally, then transfer it back.
For various and sundry reasons, our security / network architects have rejected the first two approaches. The second approach is obviously a security hole; if the remote computer is compromised, the local computer is now at risk. The first approach is unsatisfactory because the newly mounted drive is a shared resource available to other programs on the local computer during file access by the program. Even though it's quite possible to make this temporary, it's still a hole in their opinion.
They're open to the third option, but the remote network admins insist on SFTP rather than FTPS, and FtpWebRequest only supports FTPS. SFTP is the more firewall-friendly option and there are a couple libraries I could use for that approach, but I'd prefer to reduce my dependencies if I can.
I've searched MSDN for either a managed or a win32 means of using remote file sharing, but I have failed to come up with anything useful.
And so I ask: Is there another way? Did I miss a super-secret win32 function that does what I want? Or must I pursue some variant of option 3?
The way to solve your problem is to use a Win32 API called WNetUseConnection.
Use this function to connect to a UNC path with authentication, NOT to map a drive.
This will allow you to connect to a remote machine, even if it is not on the same domain, and even if it has a different username and password.
Once you have used WNetUseConnection you will be able to access the file via a UNC path as if you were on the same domain. The best way is probably through the administrative built in shares.
Example: \\computername\c$\program files\Folder\file.txt
Here is some sample C# code that uses WNetUseConnection.
Note, for the NetResource, you should pass null for the lpLocalName and lpProvider. The dwType should be RESOURCETYPE_DISK. The lpRemoteName should be \\ComputerName.
using System;
using System.Runtime.InteropServices ;
using System.Threading;
namespace ExtremeMirror
{
public class PinvokeWindowsNetworking
{
#region Consts
const int RESOURCE_CONNECTED = 0x00000001;
const int RESOURCE_GLOBALNET = 0x00000002;
const int RESOURCE_REMEMBERED = 0x00000003;
const int RESOURCETYPE_ANY = 0x00000000;
const int RESOURCETYPE_DISK = 0x00000001;
const int RESOURCETYPE_PRINT = 0x00000002;
const int RESOURCEDISPLAYTYPE_GENERIC = 0x00000000;
const int RESOURCEDISPLAYTYPE_DOMAIN = 0x00000001;
const int RESOURCEDISPLAYTYPE_SERVER = 0x00000002;
const int RESOURCEDISPLAYTYPE_SHARE = 0x00000003;
const int RESOURCEDISPLAYTYPE_FILE = 0x00000004;
const int RESOURCEDISPLAYTYPE_GROUP = 0x00000005;
const int RESOURCEUSAGE_CONNECTABLE = 0x00000001;
const int RESOURCEUSAGE_CONTAINER = 0x00000002;
const int CONNECT_INTERACTIVE = 0x00000008;
const int CONNECT_PROMPT = 0x00000010;
const int CONNECT_REDIRECT = 0x00000080;
const int CONNECT_UPDATE_PROFILE = 0x00000001;
const int CONNECT_COMMANDLINE = 0x00000800;
const int CONNECT_CMD_SAVECRED = 0x00001000;
const int CONNECT_LOCALDRIVE = 0x00000100;
#endregion
#region Errors
const int NO_ERROR = 0;
const int ERROR_ACCESS_DENIED = 5;
const int ERROR_ALREADY_ASSIGNED = 85;
const int ERROR_BAD_DEVICE = 1200;
const int ERROR_BAD_NET_NAME = 67;
const int ERROR_BAD_PROVIDER = 1204;
const int ERROR_CANCELLED = 1223;
const int ERROR_EXTENDED_ERROR = 1208;
const int ERROR_INVALID_ADDRESS = 487;
const int ERROR_INVALID_PARAMETER = 87;
const int ERROR_INVALID_PASSWORD = 1216;
const int ERROR_MORE_DATA = 234;
const int ERROR_NO_MORE_ITEMS = 259;
const int ERROR_NO_NET_OR_BAD_PATH = 1203;
const int ERROR_NO_NETWORK = 1222;
const int ERROR_BAD_PROFILE = 1206;
const int ERROR_CANNOT_OPEN_PROFILE = 1205;
const int ERROR_DEVICE_IN_USE = 2404;
const int ERROR_NOT_CONNECTED = 2250;
const int ERROR_OPEN_FILES = 2401;
private struct ErrorClass
{
public int num;
public string message;
public ErrorClass(int num, string message)
{
this.num = num;
this.message = message;
}
}
// Created with excel formula:
// ="new ErrorClass("&A1&", """&PROPER(SUBSTITUTE(MID(A1,7,LEN(A1)-6), "_", " "))&"""), "
private static ErrorClass[] ERROR_LIST = new ErrorClass[] {
new ErrorClass(ERROR_ACCESS_DENIED, "Error: Access Denied"),
new ErrorClass(ERROR_ALREADY_ASSIGNED, "Error: Already Assigned"),
new ErrorClass(ERROR_BAD_DEVICE, "Error: Bad Device"),
new ErrorClass(ERROR_BAD_NET_NAME, "Error: Bad Net Name"),
new ErrorClass(ERROR_BAD_PROVIDER, "Error: Bad Provider"),
new ErrorClass(ERROR_CANCELLED, "Error: Cancelled"),
new ErrorClass(ERROR_EXTENDED_ERROR, "Error: Extended Error"),
new ErrorClass(ERROR_INVALID_ADDRESS, "Error: Invalid Address"),
new ErrorClass(ERROR_INVALID_PARAMETER, "Error: Invalid Parameter"),
new ErrorClass(ERROR_INVALID_PASSWORD, "Error: Invalid Password"),
new ErrorClass(ERROR_MORE_DATA, "Error: More Data"),
new ErrorClass(ERROR_NO_MORE_ITEMS, "Error: No More Items"),
new ErrorClass(ERROR_NO_NET_OR_BAD_PATH, "Error: No Net Or Bad Path"),
new ErrorClass(ERROR_NO_NETWORK, "Error: No Network"),
new ErrorClass(ERROR_BAD_PROFILE, "Error: Bad Profile"),
new ErrorClass(ERROR_CANNOT_OPEN_PROFILE, "Error: Cannot Open Profile"),
new ErrorClass(ERROR_DEVICE_IN_USE, "Error: Device In Use"),
new ErrorClass(ERROR_EXTENDED_ERROR, "Error: Extended Error"),
new ErrorClass(ERROR_NOT_CONNECTED, "Error: Not Connected"),
new ErrorClass(ERROR_OPEN_FILES, "Error: Open Files"),
};
private static string getErrorForNumber(int errNum)
{
foreach (ErrorClass er in ERROR_LIST)
{
if (er.num == errNum) return er.message;
}
return "Error: Unknown, " + errNum;
}
#endregion
[DllImport("Mpr.dll")] private static extern int WNetUseConnection(
IntPtr hwndOwner,
NETRESOURCE lpNetResource,
string lpPassword,
string lpUserID,
int dwFlags,
string lpAccessName,
string lpBufferSize,
string lpResult
);
[DllImport("Mpr.dll")] private static extern int WNetCancelConnection2(
string lpName,
int dwFlags,
bool fForce
);
[StructLayout(LayoutKind.Sequential)] private class NETRESOURCE
{
public int dwScope = 0;
public int dwType = 0;
public int dwDisplayType = 0;
public int dwUsage = 0;
public string lpLocalName = "";
public string lpRemoteName = "";
public string lpComment = "";
public string lpProvider = "";
}
public static string connectToRemote(string remoteUNC, string username, string password)
{
return connectToRemote(remoteUNC, username, password, false);
}
public static string connectToRemote(string remoteUNC, string username, string password, bool promptUser)
{
NETRESOURCE nr = new NETRESOURCE();
nr.dwType = RESOURCETYPE_DISK;
nr.lpRemoteName = remoteUNC;
// nr.lpLocalName = "F:";
int ret;
if (promptUser)
ret = WNetUseConnection(IntPtr.Zero, nr, "", "", CONNECT_INTERACTIVE | CONNECT_PROMPT, null, null, null);
else
ret = WNetUseConnection(IntPtr.Zero, nr, password, username, 0, null, null, null);
if (ret == NO_ERROR) return null;
return getErrorForNumber(ret);
}
public static string disconnectRemote(string remoteUNC)
{
int ret = WNetCancelConnection2(remoteUNC, CONNECT_UPDATE_PROFILE, false);
if (ret == NO_ERROR) return null;
return getErrorForNumber(ret);
}
}
}
For people looking for a quick solution, you can use the NetworkShareAccesser I wrote recently (based on this answer (thanks so much!)):
Usage:
using (NetworkShareAccesser.Access(REMOTE_COMPUTER_NAME, DOMAIN, USER_NAME, PASSWORD))
{
File.Copy(#"C:\Some\File\To\copy.txt", #"\\REMOTE-COMPUTER\My\Shared\Target\file.txt");
}
WARNING: Please make absolutely sure, that Dispose of the NetworkShareAccesser is called (even if you app crashes!), otherwise an open connection will remain on Windows. You can see all open connections by opening the cmd prompt and enter net use.
The Code:
/// <summary>
/// Provides access to a network share.
/// </summary>
public class NetworkShareAccesser : IDisposable
{
private string _remoteUncName;
private string _remoteComputerName;
public string RemoteComputerName
{
get
{
return this._remoteComputerName;
}
set
{
this._remoteComputerName = value;
this._remoteUncName = #"\\" + this._remoteComputerName;
}
}
public string UserName
{
get;
set;
}
public string Password
{
get;
set;
}
#region Consts
private const int RESOURCE_CONNECTED = 0x00000001;
private const int RESOURCE_GLOBALNET = 0x00000002;
private const int RESOURCE_REMEMBERED = 0x00000003;
private const int RESOURCETYPE_ANY = 0x00000000;
private const int RESOURCETYPE_DISK = 0x00000001;
private const int RESOURCETYPE_PRINT = 0x00000002;
private const int RESOURCEDISPLAYTYPE_GENERIC = 0x00000000;
private const int RESOURCEDISPLAYTYPE_DOMAIN = 0x00000001;
private const int RESOURCEDISPLAYTYPE_SERVER = 0x00000002;
private const int RESOURCEDISPLAYTYPE_SHARE = 0x00000003;
private const int RESOURCEDISPLAYTYPE_FILE = 0x00000004;
private const int RESOURCEDISPLAYTYPE_GROUP = 0x00000005;
private const int RESOURCEUSAGE_CONNECTABLE = 0x00000001;
private const int RESOURCEUSAGE_CONTAINER = 0x00000002;
private const int CONNECT_INTERACTIVE = 0x00000008;
private const int CONNECT_PROMPT = 0x00000010;
private const int CONNECT_REDIRECT = 0x00000080;
private const int CONNECT_UPDATE_PROFILE = 0x00000001;
private const int CONNECT_COMMANDLINE = 0x00000800;
private const int CONNECT_CMD_SAVECRED = 0x00001000;
private const int CONNECT_LOCALDRIVE = 0x00000100;
#endregion
#region Errors
private const int NO_ERROR = 0;
private const int ERROR_ACCESS_DENIED = 5;
private const int ERROR_ALREADY_ASSIGNED = 85;
private const int ERROR_BAD_DEVICE = 1200;
private const int ERROR_BAD_NET_NAME = 67;
private const int ERROR_BAD_PROVIDER = 1204;
private const int ERROR_CANCELLED = 1223;
private const int ERROR_EXTENDED_ERROR = 1208;
private const int ERROR_INVALID_ADDRESS = 487;
private const int ERROR_INVALID_PARAMETER = 87;
private const int ERROR_INVALID_PASSWORD = 1216;
private const int ERROR_MORE_DATA = 234;
private const int ERROR_NO_MORE_ITEMS = 259;
private const int ERROR_NO_NET_OR_BAD_PATH = 1203;
private const int ERROR_NO_NETWORK = 1222;
private const int ERROR_BAD_PROFILE = 1206;
private const int ERROR_CANNOT_OPEN_PROFILE = 1205;
private const int ERROR_DEVICE_IN_USE = 2404;
private const int ERROR_NOT_CONNECTED = 2250;
private const int ERROR_OPEN_FILES = 2401;
#endregion
#region PInvoke Signatures
[DllImport("Mpr.dll")]
private static extern int WNetUseConnection(
IntPtr hwndOwner,
NETRESOURCE lpNetResource,
string lpPassword,
string lpUserID,
int dwFlags,
string lpAccessName,
string lpBufferSize,
string lpResult
);
[DllImport("Mpr.dll")]
private static extern int WNetCancelConnection2(
string lpName,
int dwFlags,
bool fForce
);
[StructLayout(LayoutKind.Sequential)]
private class NETRESOURCE
{
public int dwScope = 0;
public int dwType = 0;
public int dwDisplayType = 0;
public int dwUsage = 0;
public string lpLocalName = "";
public string lpRemoteName = "";
public string lpComment = "";
public string lpProvider = "";
}
#endregion
/// <summary>
/// Creates a NetworkShareAccesser for the given computer name. The user will be promted to enter credentials
/// </summary>
/// <param name="remoteComputerName"></param>
/// <returns></returns>
public static NetworkShareAccesser Access(string remoteComputerName)
{
return new NetworkShareAccesser(remoteComputerName);
}
/// <summary>
/// Creates a NetworkShareAccesser for the given computer name using the given domain/computer name, username and password
/// </summary>
/// <param name="remoteComputerName"></param>
/// <param name="domainOrComuterName"></param>
/// <param name="userName"></param>
/// <param name="password"></param>
public static NetworkShareAccesser Access(string remoteComputerName, string domainOrComuterName, string userName, string password)
{
return new NetworkShareAccesser(remoteComputerName,
domainOrComuterName + #"\" + userName,
password);
}
/// <summary>
/// Creates a NetworkShareAccesser for the given computer name using the given username (format: domainOrComputername\Username) and password
/// </summary>
/// <param name="remoteComputerName"></param>
/// <param name="userName"></param>
/// <param name="password"></param>
public static NetworkShareAccesser Access(string remoteComputerName, string userName, string password)
{
return new NetworkShareAccesser(remoteComputerName,
userName,
password);
}
private NetworkShareAccesser(string remoteComputerName)
{
RemoteComputerName = remoteComputerName;
this.ConnectToShare(this._remoteUncName, null, null, true);
}
private NetworkShareAccesser(string remoteComputerName, string userName, string password)
{
RemoteComputerName = remoteComputerName;
UserName = userName;
Password = password;
this.ConnectToShare(this._remoteUncName, this.UserName, this.Password, false);
}
private void ConnectToShare(string remoteUnc, string username, string password, bool promptUser)
{
NETRESOURCE nr = new NETRESOURCE
{
dwType = RESOURCETYPE_DISK,
lpRemoteName = remoteUnc
};
int result;
if (promptUser)
{
result = WNetUseConnection(IntPtr.Zero, nr, "", "", CONNECT_INTERACTIVE | CONNECT_PROMPT, null, null, null);
}
else
{
result = WNetUseConnection(IntPtr.Zero, nr, password, username, 0, null, null, null);
}
if (result != NO_ERROR)
{
throw new Win32Exception(result);
}
}
private void DisconnectFromShare(string remoteUnc)
{
int result = WNetCancelConnection2(remoteUnc, CONNECT_UPDATE_PROFILE, false);
if (result != NO_ERROR)
{
throw new Win32Exception(result);
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
this.DisconnectFromShare(this._remoteUncName);
}
}
AFAIK, you don't need to map the UNC path to a drive letter in order to establish credentials for a server. I regularly used batch scripts like:
net use \\myserver /user:username password
:: do something with \\myserver\the\file\i\want.xml
net use /delete \\my.server.com
However, any program running on the same account as your program would still be able to access everything that username:password has access to. A possible solution could be to isolate your program in its own local user account (the UNC access is local to the account that called NET USE).
Note: Using SMB accross domains is not quite a good use of the technology, IMO. If security is that important, the fact that SMB lacks encryption is a bit of a damper all by itself.
Rather than WNetUseConnection, I would recommend NetUseAdd. WNetUseConnection is a legacy function that's been superceded by WNetUseConnection2 and WNetUseConnection3, but all of those functions create a network device that's visible in Windows Explorer. NetUseAdd is the equivalent of calling net use in a DOS prompt to authenticate on a remote computer.
If you call NetUseAdd then subsequent attempts to access the directory should succeed.
While I don't know myself, I would certainly hope that #2 is incorrect...I'd like to think that Windows isn't going to AUTOMATICALLY give out my login information (least of all my password!) to any machine, let alone one that isn't part of my trust.
Regardless, have you explored the impersonation architecture? Your code is going to look similar to this:
using (System.Security.Principal.WindowsImpersonationContext context = System.Security.Principal.WindowsIdentity.Impersonate(token))
{
// Do network operations here
context.Undo();
}
In this case, the token variable is an IntPtr. In order to get a value for this variable, you'll have to call the unmanaged LogonUser Windows API function. A quick trip to pinvoke.net gives us the following signature:
[System.Runtime.InteropServices.DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out IntPtr phToken
);
Username, domain, and password should seem fairly obvious. Have a look at the various values that can be passed to dwLogonType and dwLogonProvider to determine the one that best suits your needs.
This code hasn't been tested, as I don't have a second domain here where I can verify, but this should hopefully put you on the right track.
Here a minimal POC class w/ all the cruft removed
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
public class UncShareWithCredentials : IDisposable
{
private string _uncShare;
public UncShareWithCredentials(string uncShare, string userName, string password)
{
var nr = new Native.NETRESOURCE
{
dwType = Native.RESOURCETYPE_DISK,
lpRemoteName = uncShare
};
int result = Native.WNetUseConnection(IntPtr.Zero, nr, password, userName, 0, null, null, null);
if (result != Native.NO_ERROR)
{
throw new Win32Exception(result);
}
_uncShare = uncShare;
}
public void Dispose()
{
if (!string.IsNullOrEmpty(_uncShare))
{
Native.WNetCancelConnection2(_uncShare, Native.CONNECT_UPDATE_PROFILE, false);
_uncShare = null;
}
}
private class Native
{
public const int RESOURCETYPE_DISK = 0x00000001;
public const int CONNECT_UPDATE_PROFILE = 0x00000001;
public const int NO_ERROR = 0;
[DllImport("mpr.dll")]
public static extern int WNetUseConnection(IntPtr hwndOwner, NETRESOURCE lpNetResource, string lpPassword, string lpUserID,
int dwFlags, string lpAccessName, string lpBufferSize, string lpResult);
[DllImport("mpr.dll")]
public static extern int WNetCancelConnection2(string lpName, int dwFlags, bool fForce);
[StructLayout(LayoutKind.Sequential)]
public class NETRESOURCE
{
public int dwScope;
public int dwType;
public int dwDisplayType;
public int dwUsage;
public string lpLocalName;
public string lpRemoteName;
public string lpComment;
public string lpProvider;
}
}
}
You can directly use \\server\share\folder w/ WNetUseConnection, no need to strip it to \\server part only beforehand.
Most SFTP servers support SCP as well which can be a lot easier to find libraries for. You could even just call an existing client from your code like pscp included with PuTTY.
If the type of file you're working with is something simple like a text or XML file, you could even go so far as to write your own client/server implementation to manipulate the file using something like .NET Remoting or web services.
im attach my vb.net code based on brian reference
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Public Class PinvokeWindowsNetworking
Const NO_ERROR As Integer = 0
Private Structure ErrorClass
Public num As Integer
Public message As String
Public Sub New(ByVal num As Integer, ByVal message As String)
Me.num = num
Me.message = message
End Sub
End Structure
Private Shared ERROR_LIST As ErrorClass() = New ErrorClass() {
New ErrorClass(5, "Error: Access Denied"),
New ErrorClass(85, "Error: Already Assigned"),
New ErrorClass(1200, "Error: Bad Device"),
New ErrorClass(67, "Error: Bad Net Name"),
New ErrorClass(1204, "Error: Bad Provider"),
New ErrorClass(1223, "Error: Cancelled"),
New ErrorClass(1208, "Error: Extended Error"),
New ErrorClass(487, "Error: Invalid Address"),
New ErrorClass(87, "Error: Invalid Parameter"),
New ErrorClass(1216, "Error: Invalid Password"),
New ErrorClass(234, "Error: More Data"),
New ErrorClass(259, "Error: No More Items"),
New ErrorClass(1203, "Error: No Net Or Bad Path"),
New ErrorClass(1222, "Error: No Network"),
New ErrorClass(1206, "Error: Bad Profile"),
New ErrorClass(1205, "Error: Cannot Open Profile"),
New ErrorClass(2404, "Error: Device In Use"),
New ErrorClass(2250, "Error: Not Connected"),
New ErrorClass(2401, "Error: Open Files")}
Private Shared Function getErrorForNumber(ByVal errNum As Integer) As String
For Each er As ErrorClass In ERROR_LIST
If er.num = errNum Then Return er.message
Next
Try
Throw New Win32Exception(errNum)
Catch ex As Exception
Return "Error: Unknown, " & errNum & " " & ex.Message
End Try
Return "Error: Unknown, " & errNum
End Function
<DllImport("Mpr.dll")>
Private Shared Function WNetUseConnection(ByVal hwndOwner As IntPtr, ByVal lpNetResource As NETRESOURCE, ByVal lpPassword As String, ByVal lpUserID As String, ByVal dwFlags As Integer, ByVal lpAccessName As String, ByVal lpBufferSize As String, ByVal lpResult As String) As Integer
End Function
<DllImport("Mpr.dll")>
Private Shared Function WNetCancelConnection2(ByVal lpName As String, ByVal dwFlags As Integer, ByVal fForce As Boolean) As Integer
End Function
<StructLayout(LayoutKind.Sequential)>
Private Class NETRESOURCE
Public dwScope As Integer = 0
Public dwType As Integer = 0
Public dwDisplayType As Integer = 0
Public dwUsage As Integer = 0
Public lpLocalName As String = ""
Public lpRemoteName As String = ""
Public lpComment As String = ""
Public lpProvider As String = ""
End Class
Public Shared Function connectToRemote(ByVal remoteUNC As String, ByVal username As String, ByVal password As String) As String
Return connectToRemote(remoteUNC, username, password, False)
End Function
Public Shared Function connectToRemote(ByVal remoteUNC As String, ByVal username As String, ByVal password As String, ByVal promptUser As Boolean) As String
Dim nr As NETRESOURCE = New NETRESOURCE()
nr.dwType = ResourceTypes.Disk
nr.lpRemoteName = remoteUNC
Dim ret As Integer
If promptUser Then
ret = WNetUseConnection(IntPtr.Zero, nr, "", "", Connects.Interactive Or Connects.Prompt, Nothing, Nothing, Nothing)
Else
ret = WNetUseConnection(IntPtr.Zero, nr, password, username, 0, Nothing, Nothing, Nothing)
End If
If ret = NO_ERROR Then Return Nothing
Return getErrorForNumber(ret)
End Function
Public Shared Function disconnectRemote(ByVal remoteUNC As String) As String
Dim ret As Integer = WNetCancelConnection2(remoteUNC, Connects.UpdateProfile, False)
If ret = NO_ERROR Then Return Nothing
Return getErrorForNumber(ret)
End Function
Enum Resources As Integer
Connected = &H1
GlobalNet = &H2
Remembered = &H3
End Enum
Enum ResourceTypes As Integer
Any = &H0
Disk = &H1
Print = &H2
End Enum
Enum ResourceDisplayTypes As Integer
Generic = &H0
Domain = &H1
Server = &H2
Share = &H3
File = &H4
Group = &H5
End Enum
Enum ResourceUsages As Integer
Connectable = &H1
Container = &H2
End Enum
Enum Connects As Integer
Interactive = &H8
Prompt = &H10
Redirect = &H80
UpdateProfile = &H1
CommandLine = &H800
CmdSaveCred = &H1000
LocalDrive = &H100
End Enum
End Class
how to use it
Dim login = PinvokeWindowsNetworking.connectToRemote("\\ComputerName", "ComputerName\UserName", "Password")
If IsNothing(login) Then
'do your thing on the shared folder
PinvokeWindowsNetworking.disconnectRemote("\\ComputerName")
End If
I looked to MS to find the answers. The first solution assumes the user account running the application process has access to the shared folder or drive (Same domain). Make sure your DNS is resolved or try using IP address. Simply do the following:
DirectoryInfo di = new DirectoryInfo(PATH);
var files = di.EnumerateFiles("*.*", SearchOption.AllDirectories);
If you want across different domains .NET 2.0 with credentials follow this model:
WebRequest req = FileWebRequest.Create(new Uri(#"\\<server Name>\Dir\test.txt"));
req.Credentials = new NetworkCredential(#"<Domain>\<User>", "<Password>");
req.PreAuthenticate = true;
WebResponse d = req.GetResponse();
FileStream fs = File.Create("test.txt");
// here you can check that the cast was successful if you want.
fs = d.GetResponseStream() as FileStream;
fs.Close();