Accessing mapped folder from a Windows Service written in C# - c#

I have a windows service which polls for a specific folder for creation of new files. This works fine when the folder is in one of the local drives such as C: or D:
The service fails to find a folder on a mapped drive.
Here is the code which does the checking for folder exist before polling:
System.Security.Principal.WindowsIdentity userIdentity =
System.Security.Principal.WindowsIdentity.GetCurrent();
System.Security.Principal.WindowsPrincipal principal =
new System.Security.Principal.WindowsPrincipal(userIdentity);
MappedDriveResolver mdr = new MappedDriveResolver();
if (mdr.isNetworkDrive(folderPath))
{
LoggingAppWrapper.LogDeveloperMessage(folderPath + " is on a Mapped drive", 1, TraceEventType.Information, string.Empty);
}
MappedDriveResolver is a class that I found hereHow do I determine a mapped drive's actual path?
The code in that link works fine from a simple console application, but fails when it is part of windows service.
Any suggestions as to what has to be done for the code to work for a windows service?
Regards.

I would recommend you configure your service to use UNC paths for folders not on the server running the service.
Mapped drives are a usability feature for users and as such they are specific to that users profile/environment. Meaning, when you login you may have a drive X: that is mapped to \\server1\share1 but when I login my drive X: could be mapped to \\server2\share2 instead. The actual mapping process is either saved as part of your profile with the "Reconnect at logon" or is handled by a logon script.
You need to check what account the service is running under and make sure that mapped drive exists for that user environment (This might help How to map a network drive to be used by a service).
Edit:
The reason your console application works and the service doesn't is because of the differences between the environment they are running in.
To illustrate this, take this console application, compile it and then run it as a Schedule Task. Set the "path" variable to be a mapped drive that your user can access.
static void Main(string[] args) {
MappedDriveResolver mdr = new MappedDriveResolver();
string logfile;
string path = #"I:\";
string[] files;
// Write out "log" file to where this is running from
logfile = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
logfile = Path.Combine(logfile, "log.txt");
using (StreamWriter sw = new StreamWriter(logfile, true)) {
try {
sw.WriteLine("Checking path " + path);
if (mdr.isNetworkDrive(path)) {
sw.WriteLine("Network Drive: Yes");
} else {
sw.WriteLine("Network Drive: No");
}
} catch (Exception ex) {
sw.WriteLine("Exception: " + ex.Message);
}
try {
sw.WriteLine("Resolve path " + path);
string newpath = mdr.ResolveToUNC(path);
sw.WriteLine("Resolved path " + newpath);
} catch (Exception ex) {
sw.WriteLine("Exception: " + ex.Message);
}
try {
sw.WriteLine("Get file list from " + path);
files = Directory.GetFiles(path);
if (files == null || files.Length == 0) {
sw.WriteLine("No files found");
} else {
sw.WriteLine(string.Format("Found {0} files.", files.Length));
}
} catch (Exception ex) {
sw.WriteLine("Exception: " + ex.Message);
}
sw.Flush();
sw.Close();
}
}
Note: This is with the Windows 7 Task Scheduler
Test 1: Just run the app by double-clicking on it.
Result: Success
Test 2: Configure scheduled task to run as your user account with "Run only when user is logged on"
Result: Success
Test 3: Configure scheduled task to run as your user account with "Run whether user is logged on or not"
Result: Exceptions
Test 4: Configure schedule task to run as "Local Service" account.
Result: Exceptions
Test 1 & 2 work because they are using the currently logged in user environment including the mapped drives that are part of it.
Test 3 & 4 fail because they have their own user environment created for them, which does not have any mapped drives configured. It escapes me at the moment what the differences there are, but an "interactive" and "non-interactive" environment are different in some significant ways.

Related

C# File.copy and Directory.CreateDirectory working on Win10 but in Win7 it appends folder to parent folder

The same code, one on windows 10, the other on windows 7.
The idea is to have a directory from a network drive replicate over to a local drive.
On windows 10, the machine I am writing it on, it works perfectly fine as intended.
On windows 7, the target machine, it 'works' but the sub folder structure is messed up.
Example,
C:\target -> the target location
C:\targetNewFolderName1 -> What its being copied to
C:\targetNewFolderName2
C:\targetNewFolderNameN
When it should be doing this below,(which it is, on windows 10, not on windows 7)
C:\target -> the target location
C:\target\NewFolderName1 -> What its being copied to
C:\target\NewFolderName2
C:\target\NewFolderNameN
Master is a network directory, #"\\server\fu\bar\target"
Slave is a local directory, #"C:\target"
These are passed to the function.
Function header, private void CheckMasterToSlave(string MasterPath, string SlavePath, string BackupPath, string[] MasterFilesList, string[] SlaveFilesList)
The below code snipit is within a foreach; foreach (string master in MasterFilesList).
log.Info(master + " doesnt exist, copying");
string directoryCheck = (SlavePath + master.Substring(MasterPath.Length)).Substring(0,
(SlavePath + master.Substring(MasterPath.Length)).LastIndexOf("\\"));
if (!Directory.Exists(directoryCheck))
{
log.Debug(directoryCheck + " Directory not present, touching.");
try
{
Directory.CreateDirectory((SlavePath +
master.Substring(MasterPath.Length)).Substring(0, (SlavePath +
master.Substring(MasterPath.Length)).LastIndexOf("\\")));
}
catch
{
log.Error(master + " directory failed to be created in slave environment.");
}
}
try
{
File.Copy(master, SlavePath + master.Substring(MasterPath.Length));
log.Info(SlavePath + master.Substring(MasterPath.Length) + " Successfully created.");
BackupFile(master.Replace(MasterPath, SlavePath), BackupPath, SlavePath);
}
catch
{
log.Error(master + " failed to copy, backup has been halted for this file.");
}
I do not understand why this works as intended on windows 10 but moving it to windows 7 causes this issue.
What would be causing this and how can I stop the new folder from appending to the parent folder in windows 7?
Use Path.Combine to build a path name from different path components instead of just using string concatenation.
Alright, I am stupid and forgot to change to release. When changes that NineBerry mentioned were made. It did work.
I still do not understand why the original did work on windows 10 but not on windows 7. Especially since the BackupFile portion does the same thing as the old 'wrong' way. But both work now.
Regardless, here is the updated bit.
log.Info(master + " doesnt exist, copying");
string[] EndDirectoryFile = master.Substring(MasterPath.Length).Split('\\');
string[] EndDirectory = new string[EndDirectoryFile.Length-1];
for (int i = 0; i < EndDirectoryFile.Length - 1; i++)
{
EndDirectory[i] = EndDirectoryFile[i];
}
string directoryCheck = Path.Combine(SlavePath, Path.Combine(EndDirectory));
if (!Directory.Exists(directoryCheck))
{
log.Debug(directoryCheck + " Directory not present, touching.");
try
{
Directory.CreateDirectory(directoryCheck);
}
catch
{
log.Error(master + " directory failed to be created in slave environment.");
}
}
try
{
File.Copy(master, SlavePath + master.Substring(MasterPath.Length));
log.Info(SlavePath + master.Substring(MasterPath.Length) + " Successfully created.");
BackupFile(master.Replace(MasterPath, SlavePath), BackupPath, SlavePath);
}
catch
{
log.Error(master + " failed to copy, backup has been halted for this file.");
}

How to get 'Log On As' information from local Windows Services

From what I can see 'ServiceController' doesn't have the option to pull the 'Log On As' information from local Windows Services.
Currently I'm using ServiceController to provide me with the Display Name of the service and the current status but I would also like to pull in the 'Log On As' information too.
I did see wmic can get this information from startname using:
wmic service get name,startname
Currently this is my code:
public string GetLocalServices()
{
var sc = new System.ServiceProcess.ServiceController();
ServiceController[] svcList = ServiceController.GetServices();
try
{
foreach (ServiceController service in ServiceController.GetServices())
{
string serviceDisplayName = service.DisplayName;
string status = service.Status.ToString();
var serviceDetails = "Display Name: " + serviceDisplayName + "Service Status: " + status + "\r\n" + "\r\n";
File.AppendAllText(Path.Combine(_TempPath, #"LocalServices.txt"), serviceDetails.ToString());
}
return "Retrieving Local Services Information complete.";
}
catch (Exception ex)
{
return string.Concat("!!! Exception: Unable to gather information on local services ", ex.Message);
}
}
Is anyone aware of how I can query wmic in this class and append this to the serviceDetails variable so I can print out the Log On As information too?
Take a look at the WMI code creator tool from microsoft(link below to download). This tool gives you the snippets of code in C# for what you are looking for.
https://www.microsoft.com/en-us/download/details.aspx?id=8572
Click on the link to see an image of how the tool works.
WMI Code Creator

System.IO is there a way to detect delayed write failure (from WebDAV)?

So I have a C# console app that goes through a huge list of files and copies missing files to a remote location using a WebDAV mapped drive.
In essence, I'm not using a custom client, I'm using windows built-in client.
net use j: http://127.69.69.69 /user:testme pass /persistent:yes
Everything seems to work just fine (I've increased the file size limits on the IIS 7.0 server, etc. ) EXCEPT occasionally I get a "Delayed Write Failure" popup from the OS.
My problem is that my application does not bail/throw an exception on the File.Copy() Method on the client-- How might I detect this?
I guess it's not a huge deal, but part of me wonders why no exception is thrown. It appears to move onto the next file without logging an error. My script checks if the remote file size is the same and can replace a partial file on the next cycle.
Pretty simple code where it actually copies:
Log("\n copying " + lf.FullName);
try
{
if (!Directory.Exists(Path.Combine(remotePath, localDir.Name)))
Directory.CreateDirectory(Path.Combine(remotePath, localDir.Name));
}
catch (Exception e)
{
Log("Cannot create remote directory " + Path.Combine(remotePath, localDir.Name) + " " + e.Message , "error");
}
try
{
File.Copy(lf.FullName, Path.Combine(new string[] { remotePath, localDir.Name, lf.Name }), true);
}
catch (Exception e)
{
Log("Cannot copy file to " + Path.Combine(new string[] { remotePath, localDir.Name, lf.Name }) + " " + e.Message, "error");
}

ASP.NET ascx control hosted in Sharepoint gets System.UnauthorizedAccessException when trying to save file

We have an ascx custom control (not a web part) hosted in a special Sharepoint page. This page allows users to upload files to our server. Unfortunately permission issues are preventing Sharepoint from saving files to the network location.
The network account attributed to the application pool for the Sharepoint 2007 based site has "modify" and "read" access granted to the location.
We've logged in to a different machine using the credentials used by the application pool account and can create directories and files without any issue at the specified network location.
Is it possible Sharepoint is trying to use some other account to save these files rather than the one set on it's Application Pool in IIS7?
The error we're getting:
Message: Access to the path '\opal\gwl\pictures\L36' is denied.
Stack Trace: at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) at System.IO.Directory.InternalCreateDirectory(String fullPath, String path, DirectorySecurity dirSecurity) at System.IO.Directory.CreateDirectory(String path, DirectorySecurity directorySecurity) at ECan.SharePoint.Web.Applications.MyECan_WaterMeterFormDatalogger.SavePhotos()
Exception Type: System.UnauthorizedAccessException
User: System Account
The code for the SavePhotos function in the ascx code behind file:
protected void SavePhotos()
{
string wellNo = WellNo.Value;
string epoWaterMeterID = EPO_WaterMeterID.Value;
string dirRoot = ConfigurationManager.AppSettings["PhotoDir"];
string map = wellNo.Substring(0, wellNo.IndexOf('/'));
int photoSaveCount = 1;
foreach (string filePath in Request.Files)
{
HttpPostedFile file = (HttpPostedFile)Request.Files[filePath];
if (file.InputStream.Length > 0)
{
try
{
// Create dir if does not exist
string dir = dirRoot + map;
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
// Save file
file.SaveAs(dir + #"\" + wellNo.Replace('/', '_') + "-" + epoWaterMeterID.ToString() + "-" + photoSaveCount.ToString() + ".jpg");
photoSaveCount++;
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
}
}
Anyone have any ideas what the issue might be?
I think you have to call the SavePhotos with elevated privildges.
Running the code with elevated priviledges will executes the specified method with Full Control rights even if the user does not otherwise have Full Control.
See link:
http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spsecurity.runwithelevatedprivileges(v=office.12).aspx
Please try the below code:
protected void Button1_Click(object sender, EventArgs e)
{
SPSecurity.CodeToRunElevated elevatedGetSitesAndGroups = new SPSecurity.CodeToRunElevated(SavePhotos);
SPSecurity.RunWithElevatedPrivileges(elevatedGetSitesAndGroups);
}
Have you tried to set the permission of the newly created directory or folder? You can do so by using the DirectorySecurity class within the System.Security.AccessControl Namespace, and specifically the SetAccessControl Method of that class.

Copy files between 2 protected sources that are not in the same Domain

I have a small deploy tool that I'm upgrading. The tool takes a version of code from the build box, updates SVN, and then plops it on X servers (A deploy moves specific parts of the deploy installs to different servers within the stack).
What is happening now is when it's ran on anything other than our build box, it will not work due to securities.
Our build box is internal and on our own domain. The servers we're copying to are on a high security domain. I have used the techniques explained here: Accessing Password Protected Network Drives in Windows in C#? for accessing files / data on those domain drives so i don't need to map it.
But here's the catch.
Build box - Domain A
Deploy Server - Domain B
Deploy Server 2 - Domain B
My box has complete control over our Build Box because the dev's run as administrators, and it is on our domain. However, once I impersonate my login so I'm on Domain B, I can't access my Domain A build box.
This is an internal utility, and any help would be appreciated.
*If there's extensive work on this instead of copying I can open new threads and run a command line to get these files from SVN on each server as that is a possibility instead of copying. We keep all deploy install files in SVN.
IntPtr token;
if (!Security.Access.LogonUser("ChuckNorris", "a_small_bunny[0]", "OfficeSpace", Security.Enums.LogonType.NewCredentials, Security.Enums.LogonProvider.Default, out token))
{
throw new Win32Exception();
}
try
{
IntPtr dToken;
if (!Security.Access.DuplicateToken(token, Security.Enums.SecurityImpersonationLevel.Impersonation, out dToken))
throw new Win32Exception();
try
{
using (WindowsImpersonationContext iContext = new WindowsIdentity(dToken).Impersonate())
{
Directory.CreateDirectory(destDir); //Works Here as I have impersonation
// copy each file to destination
//This will bomb as my user is now linked to the prod domain.
foreach (string file in Directory.GetFiles(srcDir))
{
// update property bag
UpdatePropertyBag(
propertyBag,
PropertyBag.Step,
"Copying [" + file + "] to [" + destDir + "]");
// copy each file
File.Copy(file, CombinePath(destDir, Path.GetFileName(file)));
}
// deal with each file/folder
foreach (string dir in Directory.GetDirectories(srcDir))
{
// copy each subdirectory
CopyDirectory(propertyBag, srcDir, destDir, Path.GetFileName(dir));
}
iContext.Undo();
}
}
catch (Exception ex)
{
}
finally
{
if (dToken != IntPtr.Zero)
{
if (!Security.Access.CloseHandle(dToken))
{
// Uncomment if you need to know this case.
////throw new Win32Exception();
}
}
}
}
catch (Exception ex)
{
}
finally
{
if (token != IntPtr.Zero)
{
if (!Security.Access.CloseHandle(token))
{
// Uncomment if you need to know this case.
////throw new Win32Exception();
}
}
}
I may have missed something in the flow above but can you:
Impersonate domain A
Copy to a shared location with permissions for both domains.
Impersonate domain b, move to final location.
Other options are to read the file details, load into memory, and write to the destination and preserve timestamp if necessary.

Categories

Resources