Reputation: 351
I'm creating file from windows service run under LocalSystem account. I have a windows application that monitors the specified folder where the file is created. I'm using FileSystemWatcher but it doesn't fire. The file icon in Windows explorer is a padlock icon. How can I create this file from the windows service, so that it is accessible from the windows user account?
Upvotes: 2
Views: 3041
Reputation: 7545
I think it would be better to build a service to watch that folder, personally, since then you could set the account on that service to LocalSystem and it should all be fine if it's on the same computer as your other service that is creating the file. But the code should be similar to do it from an app, I suppose, but with an extra layer: impersonation. It really has nothing to do with if you use FileSystemWatcher
or not. Permissions are permissions, and a separate animal unto themselves.
The way I did a similar file check for a service I built, I did it with a timer. You could use that with the combination of looking for file attributes, like File.Exists()
and File.GetCreationTime()
and File.GetLastWriteTime()
to determine if the file is there, when it got there, and when it was last modified. You would know if you processed that file already or not if it is older than the interval your timer runs at.
Reference: http://www.csharp-examples.net/file-creation-modification-time/
For me, whenever I process the file, I have my application delete it, so all I have to do is the File.Exists()
check to know if I have another one.
This code below was a service where I was looking for a file at regular intervals, and I have also slightly integrated in the next part you'll need to do, that I didn't, for impersonation, which I explain below it. You might be able to adapt this to your application:
using System;
using System.ServiceProcess;
using System.Threading;
using System.Timers;
namespace MyNamespace
{
public partial class Service1: ServiceBase
{
Thread syncThread = null;
System.Timers.Timer timer1;
string filePath = @"C:\myfile.txt";
public Service1()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
timer1 = new System.Timers.Timer();
timer1.Interval = 60000; // 1 min
timer1.Enabled = true;
timer1.Elapsed += new ElapsedEventHandler(timer1_Elapsed);
timer1.Start();
}
protected override void OnStop()
{
syncThread.Abort();
timer1.Stop();
}
protected void timer1_Elapsed(object sender, ElapsedEventArgs e)
{
syncThread = new Thread(new ThreadStart(doThread));
syncThread.Start();
}
protected void doThread()
{
// This will run for each timer interval that elapses
// in its own separate thread, and each thread will
// end when the processing in this function ends
// You'll need to develop a strategy for getting these
// into your app
string username, domainName, password;
// Log the domain service account in, here...
bool returnValue = LogonUser(userName, domainName, password,
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
out safeTokenHandle);
if (!returnValue) return; // not logged in - report this
using (WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle()))
{
using (WindowsImpersonationContext impersonatedUser = newId.Impersonate())
{
bool fileAbsent = true;
string myFileText = String.Empty;
if (File.Exists(filePath))
{
fileAbsent = false;
FileStream fs = new FileStream(filePath, FileMode.Open);
StreamReader sr = new StreamReader(fs)
myFileText = sr.ReadToEnd();
sr.Close();
fs.Close();
File.Delete(filePath);
}
else
{
// report that file does not exist
}
if (myFileText != String.Empty) // content found
{
// do processing here
}
else
{
//if (fileAbsent == false)
// report file was found, but empty
}
}
}
}
}
}
Note that if someone does not have permission to open the file your app is watching for, and they are logged in, and they run this application you're building, it will not be able to open it, as you found out. The application uses the same file permissions as the logged-in user, unless you implement code to do otherwise with a WindowsImpersonationContext
for the user that does have permissions to that file. But you would not be able to impersonate the Windows LocalSystem account in your application because you would not have its credentials, and it is a best practice to run a service under a domain account if it needs extra permissions, like this, because of that. Instead, you should set the service to run under a new or existing domain service account that should have permissions to the file where you know its password. Then you can impersonate that domain account within your application as if you had actually logged in with that account.
This class below, copied and formatted from the MSDN site, is something like what you'd have to adapt to your application - you would have to combine it with the code for the timer, above. It would be where you would either have to prompt for, or store, the credentials of your domain account that has access to the file. Inside the area with using (WindowsImpersonationContext impersonatedUser = newId.Impersonate()) { ... }
is where you would do the file reading and processing as the impersonated user. I have added that, and the LogonUser()
call, to the code above, but you would have to include that function, the constants, and all the other missing references from below that I didn't include:
public class ImpersonationDemo
{
[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);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
// Test harness.
// If you incorporate this code into a DLL, be sure to demand FullTrust.
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public static void Main(string[] args)
{
SafeTokenHandle safeTokenHandle;
try
{
string userName, domainName;
// Get the user token for the specified user, domain, and password using the
// unmanaged LogonUser method.
// The local machine name can be used for the domain name to impersonate a user on this machine.
Console.Write("Enter the name of the domain on which to log on: ");
domainName = Console.ReadLine();
Console.Write("Enter the login of a user on {0} that you wish to impersonate: ", domainName);
userName = Console.ReadLine();
Console.Write("Enter the password for {0}: ", userName);
const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;
// Call LogonUser to obtain a handle to an access token.
bool returnValue = LogonUser(userName, domainName, Console.ReadLine(),
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
out safeTokenHandle);
Console.WriteLine("LogonUser called.");
if (false == returnValue)
{
int ret = Marshal.GetLastWin32Error();
Console.WriteLine("LogonUser failed with error code : {0}", ret);
throw new System.ComponentModel.Win32Exception(ret);
}
using (safeTokenHandle)
{
Console.WriteLine("Did LogonUser Succeed? " + (returnValue ? "Yes" : "No"));
Console.WriteLine("Value of Windows NT token: " + safeTokenHandle);
// Check the identity.
Console.WriteLine("Before impersonation: "
+ WindowsIdentity.GetCurrent().Name);
// Use the token handle returned by LogonUser.
using (WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle()))
{
using (WindowsImpersonationContext impersonatedUser = newId.Impersonate())
{
// Check the identity.
Console.WriteLine("After impersonation: "
+ WindowsIdentity.GetCurrent().Name);
}
}
// Releasing the context object stops the impersonation
// Check the identity.
Console.WriteLine("After closing the context: " + WindowsIdentity.GetCurrent().Name);
}
}
catch (Exception ex)
{
Console.WriteLine("Exception occurred. " + ex.Message);
}
}
}
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);
}
}
Upvotes: 1
Reputation: 2499
The FileWatcher is flaky. There are also issues if you are watching a folder on a network drive. I have seen a dozen or more applications that use a FileWatcher and everyone of them has more than once failed to recognize when a file was created.
I would backup the FileWatcher with a timed event that checks for new or modified files. That way if the FileWatcher fails to recognize the event, the timer will catch it.
Upvotes: 2