After reading tons of similar posts I decided to came up with this one. Well, basically this problem is similar to many others, but somehow I can't make it work.
Here's the scenario, I have load balancing over 2 servers (servA and servB) and I need to force the app to create just on 1 of them. So I want to put the UNC path when I save files. I, obviously, have a problem creating files on the directory over the net.
If I run it with Cassini it's all good, I can access to the path cause it's logged with my account. As soon as I migrate the app on the development server it doesn't work anymore.
I know IIS uses the user associated with the app pool, so I checked that account (which is network_service) and added write privileges write on that folder.
Still not enough. What you think on "Everyone"?! It must work!
Oh, well, it's not.
Let's see some code:
Directory.CreateDirectory("\\\\my.ip.over.da.net\\c$\\inetpub\\wwwroot\\projfolder\\otherprojfolder\\test");
And this is the message I got when I try to create that folder.
{"Message":"Access to the path \u0027\\\\\\\\my.ip.over.da.net\\\\c$\\\\inetpub
\\\\wwwroot\\\\projfolder\\\\otherprojfolder\\\\test\u0027 is denied.","StackTrace":"
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)\r\n at
System.IO.Directory.InternalCreateDirectory(String fullPath, String path,
DirectorySecurity dirSecurity)\r\n at System.IO.Directory.CreateDirectory(String path,
DirectorySecurity directorySecurity)\r\n at
NSC.Ajax.GetData.testgrid()","ExceptionType":"System.UnauthorizedAccessException"}
It's called via AJAX for easier testing, this is why the response is formatted that way.
The problem is you're not going to have access to that location using the IIS credentials, the dev server is going to be on a separate domain somewhere else, and accessing back to your machine going to the c$ admin share isn't going to work, changing permissions at that level is.. a bit risky...
If you really have to access files on your local machine from your dev server, you'd probably be better off creating a share called test (C:\inetpub\wwwroot\projfolder\otherprojfolder\test) on your machine and set the permissions on this for Everyone to read (if you're going to need to create files and folders you'll need more, but I'd suggest only giving the minimum access you can get away with), pretty insecure though, but since your dev machine won't have any way of authenticating an account on a different network (your machine you're sharing the files from) you don't have much to play with!
So create a local shared folder, then just point your code to \\\\my.ip.over.da.net\\test.
Note, you'll need to set the permissions on the share and on the folder itself, if the share has enough permissions but the ACL on the folder doesn't agree you'll still get permissions denied.
You can impersonate a different user when creating the directory
public static void CreateDirectory(string myDirectory)
{
SafeTokenHandle safeTokenHandle;
bool returnValue = LogonUser(#Username, #Domain, #Password, 2, 0, out safeTokenHandle);
if (returnValue == true)
{
WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle());
using (WindowsImpersonationContext impersonatedUser = newId.Impersonate())
{
System.IO.Directory.CreateDirectory(myDirectory);
}
}
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken);
public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeTokenHandle()
: base(true)
{
}
[DllImport("kernel32.dll")]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr handle);
protected override bool ReleaseHandle()
{
return CloseHandle(handle);
}
}
}
More details here :
http://msdn.microsoft.com/en-us/library/system.security.principal.windowsimpersonationcontext.aspx
Related
I'm trying to use a service installed to run as a specific user (me in this case) to change the wallpaper.
Here is my Wallpaper class which has a SetWallpaper function:
public sealed class Wallpaper
{
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError=true)]
private static extern Int32 SystemParametersInfo(
UInt32 action, UInt32 uParam, String vParam, UInt32 winIni);
private static readonly UInt32 SPI_SETDESKWALLPAPER = 0x14;
private static readonly UInt32 SPIF_UPDATEINIFILE = 0x01;
private static readonly UInt32 SPIF_SENDWININICHANGE = 0x02;
public static void SetWallpaper(String path)
{
System.IO.Stream s = new System.Net.WebClient().OpenRead(path.ToString());
System.Drawing.Image img = System.Drawing.Image.FromStream(s);
string tempPath = Path.Combine(Path.GetTempPath(), "wallpaper.bmp");
ImgurWallpaperSetter.ImgurWallpaperSetter.log(tempPath);
img.Save(tempPath, System.Drawing.Imaging.ImageFormat.Bmp);
SystemParametersInfo(SPI_SETDESKWALLPAPER, 1, tempPath,
SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE);
int error = Marshal.GetLastWin32Error();
ImgurWallpaperSetter.ImgurWallpaperSetter.log("Last error: " + error);
}
}
It works perfectly when I run SetWallpaper from a unit test, but it doesn't work at all when I install the service and start it.
Here is the service start code:
protected override void OnStart(string[] args) {
//WallpaperScheduler.ScheduleWallpaperFetch(DateTime.Now.Hour, DateTime.Now.Minute+1);
//Debugger.Launch();
Uri imageUrl = WallpaperRetriever.mostPopularImgurWallpaper();
log(imageUrl.AbsoluteUri);
Wallpaper.SetWallpaper(imageUrl.AbsoluteUri);
}
I've confirmed that it's downloading the image into my temp directory correctly, but it's not setting the wallpaper. It doesn't error out or log anything to the event logs.
Here's my service installed in the local service viewer:
Running it does nothing.
A similar thread I've read
Edit:
Added this code to run on my serviceInstaller_Committed event which should allow the service to interact with the desktop, but I see a huge delay between the service run and the actual switching of the wallpaper:
ConnectionOptions coOptions = new ConnectionOptions();
coOptions.Impersonation = ImpersonationLevel.Impersonate;
ManagementScope mgmtScope = new ManagementScope(#"root\CIMV2", coOptions);
mgmtScope.Connect();
ManagementObject wmiService;
wmiService = new ManagementObject(
"Win32_Service.Name='" + serviceInstaller1.ServiceName + "'"
);
ManagementBaseObject InParam = wmiService.GetMethodParameters("Change");
InParam["DesktopInteract"] = true;
ManagementBaseObject OutParam = wmiService.InvokeMethod("Change", InParam, null);
Edit2:
I've updated my service to log to the system events GetLastError(). Now I am seeing that the service is throwing error 1459 ("This operation requires an interactive window station."). However, this doesn't explain why my wallpaper does eventually switch (I think usually after waking from sleep). Updated Wallpaper class above as well.
Edit3
I've confirmed that after sleeping, the new wallpaper is set. Can anyone explain why this is? Could it be that I need to restart for the Interactive Desktop ability to be set?
Edit4
What I'm doing is feeling pretty hacky. Would it be better if I had the service do nothing but download wallpapers and potentially had another non-service application for changing the wallpaper if new wallpapers have been downloaded and the user is logged in?
Are you aware of Session 0 Isolation? It means that your service is running in a desktop that no user will ever log into, and that restricted environment may very well be affecting your program's behavior.
You say that the code "doesn't error out or log anything to the event logs", but, based on what you have shown, you need to improve the error checking to catch more subtle problems. For example, SystemParametersInfo() returns FALSE when it fails (and a subsequent call to GetLastError() may be very informative!) but your code doesn't check for that result. You can not rely on explicit exceptions alone.
Add this class: http://pastebin.com/ERsnqMEy
Use it like this: http://pastebin.com/RYvvT7bH
Works wonders in using WMI impersonating the logged in user from a windows system service. Best of luck.
In Services, go to Properties, and check "Use Local Account" and something like "Allow Using of Desktop". I'm not sure about names, because my Windows is in a different language, but you should be able to find it.
I need to impersonate the my code to run with a different windows user id, right now i am using some code which i got from web which is calling some native libraries.
the purpose of the code is to change the permissions on a file server user directories permissions.
I can change my folder permissions, but i have the credentials of the other user to go and change the permissions on his folder. But i am running the code on my machine by impersonating him.
But, i am getting un authorized exception.
the code i am using is:
[DllImport("advapi32.dll", SetLastError = true)]
private static extern int LogonUser(
string lpszUserName,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
I am not sure this is working in Windows 7 or not. Is there any one who faced any issue like this..?
Exception i am getting:
Messagee:"{"Attempted to perform an unauthorized operation."}"
stack trace:
at System.Security.AccessControl.Win32.SetSecurityInfo(ResourceType type, String name, SafeHandle handle, SecurityInfos securityInformation, SecurityIdentifier owner, SecurityIdentifier group, GenericAcl sacl, GenericAcl dacl)
at System.Security.AccessControl.NativeObjectSecurity.Persist(String name, SafeHandle handle, AccessControlSections includeSections, Object exceptionContext)
at System.Security.AccessControl.NativeObjectSecurity.Persist(String name, AccessControlSections includeSections, Object exceptionContext)
at System.Security.AccessControl.NativeObjectSecurity.Persist(String name, AccessControlSections includeSections)
at System.Security.AccessControl.FileSystemSecurity.Persist(String fullPath)
at System.IO.Directory.SetAccessControl(String path, DirectorySecurity directorySecurity)
at FolderAccessManager.Program.Main(String[] args) in
Could you please share some solutions..?
Have a look at WindowsIdentity.Impersonate. There you'll see an example of how to go about.
Are you using a domain joined machine with ASP.NET?
I got the exact same message tring this code in a asp.net application when running with the default apppool identity. Changing the apppool user to a domain user with 'domain admin' permissions solved this problem (also Windows 7).
I'm using this code to retrieve the volume serial
[DllImport("Kernel32.dll", SetLastError = true)]
extern static bool GetVolumeInformation(string vol, StringBuilder name, int nameSize, out uint serialNum, out uint maxNameLen, out uint flags, StringBuilder fileSysName, int fileSysNameSize);
public static uint GetVolumeSerial(string strDriveLetter)
{
uint serialNum, maxNameLen, flags;
bool ok = GetVolumeInformation(strDriveLetter, null, 0, out serialNum,out maxNameLen, out flags, null, 0);
return serialNum;
}
It works great, except when I'm running as administrator through the application manifest UAC elevation it always returns 0 for mapped network drives (but it works otherwise)
Maybe the administrator user doesn't see the mapped drive for some reason (you'd think it was the other way around). Is there any way around this or simply invoking that code as the logged in user instead?
I'm on 64-bits Windows 7, but running the application in x86 mode
Mapped network drives are mapped for one user. When you connect as a different user, as far as that other user is concerned, the drive doesn't exist.
If you need several users to access the same network path, use the actual UNC path ("\\server\path"). A mapped network drive is just a convenience for humans.
If you really need several users to access the mapped network drive, you'll need to map it for each user separately. You could, for example, do this in a batch file that runs on logon of each user and calls net use (I'm not quite sure it would help with users such as SYSTEM, though).
In my C# app, I am programmatically installing an Oracle client if one is not present, which requires adding a dir to the PATH system environment variable. This all works fine, but it doesn't take effect until the user logs out/in to windows, or reboots. How can I get my app to recognize and use the new PATH var without this step? Even restarting my app would be better than requiring the user to log out/in.
Supposedly, broadcasting this change to other processes should work. Here's what I've tried, with no success:
using System.Runtime.InteropServices;
private const int HWND_BROADCAST = 0xffff;
private const int WM_WININICHANGE = 0x001a, WM_SETTINGCHANGE = WM_WININICHANGE, INI_INTL = 1;
[DllImport("user32.dll")]
private static extern int SendMessageTimeoutA(int hWnd, uint wMsg, uint wParam, string lParam, int fuFlags, int uTimeout, int lpdwResult);
int rtnVal = 0;
SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, "Environment", 2, 5000, rtnVal);
I've been told if you stop and restart the process in question, it should pick up these kinds of changes, but restarting my app doesn't do it. I suppose it could be an Oracle issue, that something about Oracle requires the login to recognize the change, I'm not sure. Thanks in advance.
Does Environment.GetEnvironmentVariable("MYVAR", EnvironmentVariableTarget.Machine) not work?
If my app is running elevated then I can
Environment.SetEnvironmentVariable("MYVAR", "cool", EnvironmentVariableTarget.Machine);
//do some other stuff...
Console.WriteLine(Environment.GetEnvironmentVariable("MYVAR", EnvironmentVariableTarget.Machine));
C:\TestApp>>TestApp.exe
cool
I don't know if this will work for other running processes but it should for your app doing the getting/setting
Your problem is only certain apps listen for that message (such as explorer) so it will not be used by your application at all. As the environment is generally inherited then restarting your app from within itself isn't going to help as it will get your current Environment block. If the user restarts from the start menu it will work (assuming the WM_SETTINGCHANGE has been broadcast).
You are best using Environment.GetEnvironmentVariable to read out the current value from the registry and merge it back into you current environment. Basically doing Environment.SetEnvironmentVariable("PATH", Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine) + ";" + (Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.User)));
In our project we setup Oracle Instant Client with use of "install.bat" from Instant Client archive. For example:
install.bat odp.net1x %1 name
In the upcoming Java7, there is a new API to check if two file object are same file reference.
Are there similar API provided in the .NET framework?
I've search it over MSDN but nothing enlighten me.
I want it simple but I don't want to compare by filename which will cause problems with hard/symbolic links and different style of path. (e.g. \\?\C:\, C:\).
What I going to do is just prevent duplicated file being drag and dropped to my linklist.
As far as I can see (1) (2) (3) (4), the way JDK7 does it, is by calling GetFileInformationByHandle on the files and comparing dwVolumeSerialNumber, nFileIndexHigh and nFileIndexLow.
Per MSDN:
You can compare the VolumeSerialNumber and FileIndex members returned in the BY_HANDLE_FILE_INFORMATION structure to determine if two paths map to the same target; for example, you can compare two file paths and determine if they map to the same directory.
I do not think this function is wrapped by .NET, so you will have to use P/Invoke.
It might or might not work for network files. According to MSDN:
Depending on the underlying network components of the operating system and the type of server connected to, the GetFileInformationByHandle function may fail, return partial information, or full information for the given file.
A quick test shows that it works as expected (same values) with a symbolic link on a Linux system connected using SMB/Samba, but that it cannot detect that a file is the same when accessed using different shares that point to the same file (FileIndex is the same, but VolumeSerialNumber differs).
Edit: Note that #Rasmus Faber mentions the GetFileInformationByHandle function in the Win32 api, and this does what you want, check and upvote his answer for more information.
I think you need an OS function to give you the information you want, otherwise it's going to have some false negatives whatever you do.
For instance, does these refer to the same file?
\server\share\path\filename.txt
\server\d$\temp\path\filename.txt
I would examine how critical it is for you to not have duplicate files in your list, and then just do some best effort.
Having said that, there is a method in the Path class that can do some of the work: Path.GetFullPath, it will at least expand the path to long names, according to the existing structure. Afterwards you just compare the strings. It won't be foolproof though, and won't handle the two links above in my example.
Here is a C# implementation of IsSameFile using GetFileInformationByHandle:
NativeMethods.cs
public static class NativeMethods
{
[StructLayout(LayoutKind.Explicit)]
public struct BY_HANDLE_FILE_INFORMATION
{
[FieldOffset(0)]
public uint FileAttributes;
[FieldOffset(4)]
public FILETIME CreationTime;
[FieldOffset(12)]
public FILETIME LastAccessTime;
[FieldOffset(20)]
public FILETIME LastWriteTime;
[FieldOffset(28)]
public uint VolumeSerialNumber;
[FieldOffset(32)]
public uint FileSizeHigh;
[FieldOffset(36)]
public uint FileSizeLow;
[FieldOffset(40)]
public uint NumberOfLinks;
[FieldOffset(44)]
public uint FileIndexHigh;
[FieldOffset(48)]
public uint FileIndexLow;
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool GetFileInformationByHandle(SafeFileHandle hFile, out BY_HANDLE_FILE_INFORMATION lpFileInformation);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern SafeFileHandle CreateFile([MarshalAs(UnmanagedType.LPTStr)] string filename,
[MarshalAs(UnmanagedType.U4)] FileAccess access,
[MarshalAs(UnmanagedType.U4)] FileShare share,
IntPtr securityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
IntPtr templateFile);
}
PathUtility.cs
public static bool IsSameFile(string path1, string path2)
{
using (SafeFileHandle sfh1 = NativeMethods.CreateFile(path1, FileAccess.Read, FileShare.ReadWrite,
IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero))
{
if (sfh1.IsInvalid)
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
using (SafeFileHandle sfh2 = NativeMethods.CreateFile(path2, FileAccess.Read, FileShare.ReadWrite,
IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero))
{
if (sfh2.IsInvalid)
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
NativeMethods.BY_HANDLE_FILE_INFORMATION fileInfo1;
bool result1 = NativeMethods.GetFileInformationByHandle(sfh1, out fileInfo1);
if (!result1)
throw new IOException(string.Format("GetFileInformationByHandle has failed on {0}", path1));
NativeMethods.BY_HANDLE_FILE_INFORMATION fileInfo2;
bool result2 = NativeMethods.GetFileInformationByHandle(sfh2, out fileInfo2);
if (!result2)
throw new IOException(string.Format("GetFileInformationByHandle has failed on {0}", path2));
return fileInfo1.VolumeSerialNumber == fileInfo2.VolumeSerialNumber
&& fileInfo1.FileIndexHigh == fileInfo2.FileIndexHigh
&& fileInfo1.FileIndexLow == fileInfo2.FileIndexLow;
}
}
}
Answer: There is no foolproof way in which you can compare to string base paths to determine if they point to the same file.
The main reason is that seemingly unrelated paths can point to the exact same file do to file system redirections (junctions, symbolic links, etc ...) . For example
"d:\temp\foo.txt"
"c:\othertemp\foo.txt"
These paths can potentially point to the same file. This case clearly eliminates any string comparison function as a basis for determining if two paths point to the same file.
The next level is comparing the OS file information. Open the file for two paths and compare the handle information. In windows this can be done with GetFileInformationByHandle. Lucian Wischik did an excellent post on this subject here.
There is still a problem with this approach though. It only works if the user account performing the check is able to open both files for reading. There are numerous items which can prevent a user from opening one or both files. Including but not limited to ...
Lack of sufficient permissions to file
Lack of sufficient permissions to a directory in the path of the file
File system change which occurs between the opening of the first file and the second such as a network disconnection.
When you start looking at all of these problems you begin to understand why Windows does not provide a method to determine if two paths are the same. It's just not an easy/possible question to answer.
First I thought it is really easy but this doesn't work:
string fileName1 = #"c:\vobp.log";
string fileName2 = #"c:\vobp.log".ToUpper();
FileInfo fileInfo1 = new FileInfo(fileName1);
FileInfo fileInfo2 = new FileInfo(fileName2);
if (!fileInfo1.Exists || !fileInfo2.Exists)
{
throw new Exception("one of the files does not exist");
}
if (fileInfo1.FullName == fileInfo2.FullName)
{
MessageBox.Show("equal");
}
Maybe this library helps http://www.codeplex.com/FileDirectoryPath. I haven't used it myself.
edit: See this example on that site:
//
// Path comparison
//
filePathAbsolute1 = new FilePathAbsolute(#"C:/Dir1\\File.txt");
filePathAbsolute2 = new FilePathAbsolute(#"C:\DIR1\FILE.TXT");
Debug.Assert(filePathAbsolute1.Equals(filePathAbsolute2));
Debug.Assert(filePathAbsolute1 == filePathAbsolute2);
If you need to compare the same filenames over and over again, I would suggest you look into canonalizing those names.
Under a Unix system, there is the realpath() function which canonalizes your path. I think that's generally the best bet if you have a complex path. However, it is likely to fail on volumes mounted via network connections.
However, based on the realpath() approach, if you want to support multiple volume including network volumes, you could write your own function that checks each directory name in a path and if it references a volume then determine whether the volume reference in both paths is the same. This being said, the mount point may be different (i.e. the path on the destination volume may not be the root of that volume) so it is not that easy to solve all the problems along the way, but it is definitively possible (otherwise how would it work in the first place?!)
Once the filenames properly canonalized a simple string comparison gives you the correct answer.
Rasmus answer is probably the fastest way if you don't need to compare the same filenames over and over again.
You could always perform an MD5 encode on both and compare the result. Not exactly efficient, but easier than manually comparing the files yourself.
Here is a post on how to MD5 a string in C#.