I'm building a Setup Project so I can ship my WinUI/WPF application. In said applicaiton I use some NuGet packages that creat files on the executable's location (folder) -- these are for MSAL and WebView2. However, Windows sets only Read access to folders on the Program Files folder, so I wanted to create a custom action that gives write permission to the folder throughout the installation.
Most solutions out there are extremely outdated (.NET framework dependent) or tangents to the overall problem.
I have tried creating a Console Application, writing the following code, shipping it as a DLL/EXE and adding it as a Custom Action. My application installed but still did not have write permissions.
static void Main(string[] args)
{
string directory = args[0];
DirectoryInfo directoryInfo = new DirectoryInfo(directory);
// This gets the "Authenticated Users" group, no matter what it's called
SecurityIdentifier sid = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null);
// Create the rules
FileSystemAccessRule writerule = new FileSystemAccessRule(sid, FileSystemRights.Write, AccessControlType.Allow);
if (!string.IsNullOrEmpty(directory) && Directory.Exists(directory))
{
// Get your file's ACL
DirectorySecurity fsecurity = FileSystemAclExtensions.GetAccessControl(directoryInfo);
// Add the new rule to the ACL
fsecurity.AddAccessRule(writerule);
// Set the ACL back to the file
FileSystemAclExtensions.SetAccessControl(directoryInfo, fsecurity);
}
}
This code was based on the following thread: https://stackoverflow.com/a/10540927/16751261
Related
As part of installer code, we are trying to make changes to IIS (check and create virtual directory, followed by adding a section to its web.config file.
Installer was working fine till recently a strange error started haunting us and blocking the installer from proceeding
Error says /Core/Service/UserService failed. Reason: Filename: \?\ c:\website1\Core\Common\Service\userService\web.config Error: cannot write configuration file
Please note that "Default Web Site/Core/Service/UserService" is a virtual directory under
"Default Web Site/Core" virtual directory in IIS.
"Default Web Site/Core" has physical path of c:\website1\Core\Common whereas "Default Web Site/Core/Service/UserService" is created with physical path of "c:\website1\Core\Service\UserService"
Not sure why error is pointing towards wrong folder path for web.config.
Code we have used is like this
using (ServerManager mgr = new ServerManager())
{
//code to add virtual path to root website (default web site)
mgr.CommitChanges();
}
using (ServerManager mgrpolicy = new ServerManager())
{
Configuration config = mgrpolicy.GetWebConfiguration("Default Web Site","/Core/Service/UserService");
ConfigurationSection hndlesection = config.GetSection("system.webserver/handlers");
hndlesection["accessPolicy"] = "Read,Write"; //this value is dynamic based on installer input but hardcoded here for your reference.
mgrpolicy.CommitChanges(); //issue comes after this line
}
we are running this installer on .net 4.8 windows application where the above code runs as part of a class library pointing to namespace System.Web.Administration.
Installer user is always system administrator and thus has all privileges.
I have a C# application which stores it's settings in ProgramData subfolder such as
C:\ProgramData\Manufacturer\Product\Version\Settings.xml
I noticed that the application can't save settings changes, getting a permission denied error. My work-around was to manually change security settings and give Everyone full control on the folder tree and file. This works, but I'd like a more robust method.
Using suggestions from SO, I created the following code:
private void set_permissions()
{
try
{
// Create security idenifier for all users (WorldSid)
SecurityIdentifier sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
// get file info and add write, modify permissions
FileInfo fi = new FileInfo(settingsFile);
FileSecurity fs = fi.GetAccessControl();
FileSystemAccessRule fsar =
new FileSystemAccessRule(sid, FileSystemRights.FullControl, InheritanceFlags.None, PropagationFlags.None, AccessControlType.Allow);
fs.AddAccessRule(fsar);
fi.SetAccessControl(fs);
LogIt.LogInfo("Set permissions on Settings file");
}
catch(Exception ex)
{
LogIt.LogError(ex.Message);
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
When I step through the code, I get
Attempted to perform an unauthorized operation exception
when I execute this statement:
fi.SetAccessControl(fs);
If I close Visual Studio 2015 and open it as administrator, then my code will execute properly and the file security now has an entry for Everyone with full control.
So finally, here comes the question:
I'm following suggestion of putting the above code in my application, then in the setup project I add a custom action to run the newly installed application with an Install command-line option. My application, if it sees "Install" argument, will run the above code. Since I'm using a setup project which installs for all users by default, it automatically gives the administrator prompt before install. Does that mean the entire session, including the special action to run the application after install, is running under administrator rights?
If so, this should work, right?
But if the person installing changes it to "This user" then it would not be running with admin rights, and my code will fail. If needed, I could always be the one to do the final install and therefore would always use the administrator prompt, but I hate to depend on that.
Is there a more proper way to do this?
Thanks...
It seems that your program is not running elevated and therefore cannot update files in that location, and I assume that you want your users to not require admin privilege that you could add using an elevation manifest in your program.
So why choose that location to store the data? Why not just use User's Application Data folder?
As for that code, it's probably more robust to add it as an installer class custom action rather than run an executable. In an Everyone install that runs elevated the code will run privileged with the local system account.
i was finding how to programmatically add local service priviledge on folder in C:\Program Files by C# and got a write up from this url http://stackoverflow.com/questions/5298905/add-everyone-privilege-to-folder-using-c-net/5398398#5398398
they show how to do this for everyone user.
DirectorySecurity sec = Directory.GetAccessControl(path);
// Using this instead of the "Everyone" string means we work on non-English systems.
SecurityIdentifier everyone = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
sec.AddAccessRule(new FileSystemAccessRule(everyone, FileSystemRights.Modify | FileSystemRights.Synchronize, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow));
Directory.SetAccessControl(path, sec);
just tell me what i need to change in above code as a result local service privilege will be add on a specific folder.
i develop a windows service which will create a folder and xml file in it at run time. when i install my service from setup file then folder is not getting created but no error is also return.
so i debug the service and saw it could create folder and xml file in it during debugging time. the issue is occurred when i install the service from setup file. i am not being able to capture the issue like what problem is occuring. so guide me what should i do to capture the issue like "Why my service not being able to create folder and file " looking for guidance. thanks
new SecurityIdentifier(WellKnownSidType.LocalServiceSid, null);
or
new SecurityIdentifier("S-1-5-19");
It seems I have a strange issue with security:
I have a website with the following folders:
inetpub\wwwroot
inetpub\wwwroot\readyfordownload
The IIS APPPOOL\Classic user has full access to this 'readyfordownload' folder.
Now I have a console APP that creates a zipfile in the readyfordownload folder. This is done from a c# classlib. Strangely enough, the IIS APPOOL cannot access this file, even though it has full control over the folder. Also, the classlib first creates an xlsx file that is later added to the zip. The APPPOOL user does have access to the xlsx file.
If I run the same function in the C# classlib from a code behind in the website, the same zipfile is created and the IIS APPPOOL user CAN access the file....
Any ideas?
zip is created like this (not the actual code, but it is the same)
http://dotnetzip.codeplex.com/
using (ZipFile zip = new ZipFile())
{
// add this map file into the "images" directory in the zip archive
zip.AddFile("test.xlsx");
zip.Save("MyZipFile.zip");
}
OS is windows 2008 R2 web server
ZIP library is Dotnetzip (Ionic)
Update: I am most interested in why the ZIPfile does not get the rights and the xlsx file does....
Have you tried setting the FileAccessSecurity explicitly? Maybe the files are not inheriting the ACL from the directory.
the apppool user can access the xlsx file because your console creates it directly under readyfordownload folder.
the zip file on the other hand is first created in a temp folder and then copied to your folder. This means that the file permissions are wrongly set on the file.
Make sure IIS_IUSR and DefaultAppPool users have access on your wwwroot.
As scottm suggested change your console code to give permissions to the IUSR and DefaultAppPool users on the zip file. Your code should read like:
using (ZipFile zip = new ZipFile())
{
// add this map file into the "images" directory in the zip archive
zip.AddFile("test.xlsx");
zip.Save("MyZipFile.zip");
var accessControl = File.GetAccessControl("MyZipFile.zip");
var fileSystemAccessRule = new FileSystemAccessRule(
#"BUILTIN\IIS_IUSRS",
FileSystemRights.Read | FileSystemRights.ReadAndExecute,
AccessControlType.Allow);
var fileSystemAccessRule2 = new FileSystemAccessRule(
#"IIS AppPool\DefaultAppPool",
FileSystemRights.Read | FileSystemRights.ReadAndExecute,
AccessControlType.Allow);
accessControl.AddAccessRule(fileSystemAccessRule);
accessControl.AddAccessRule(fileSystemAccessRule2);
File.SetAccessControl(path, accessControl);
}
Check Windows EventLog for related errors. For detailed info use ProcessMonitor, so you can see if there is a problem with permissions.
Configure the security of the folder using “advanced securty setting property page”. (Select properties--> security). Also note that the application pool can impersonate the user so that the application may not be serving the request with the identity of the app pool. By default impersonation may not work. You have to set it explicitly in the web config. E.g. <identity impersonate="true" /> or <identity impersonate="true" userName="domain\user" password="password" />
Sriwantha Sri Aravinda
I want to store the username/password information of my windows service 'logon as' user in the app.config.
So in my Installer, I am trying to grab the username/password from app.config and set the property but I am getting an error when trying to install the service.
It works fine if I hard code the username/password, and fails when I try and access the app.config
public class Blah : Installer
{
public Blah()
{
ServiceProcessInstaller oServiceProcessInstaller = new ServiceProcessInstaller();
ServiceInstaller oServiceInstaller = new ServiceInstaller();
oServiceProcessInstaller.Account = ServiceAccount.User;
oServiceProcessInstaller.Username = ConfigurationManager.AppSettings["ServiceProcessUsername"].ToString();
}
}
Just some ideas on accessing config files inside an installer.
Configuration config = ConfigurationManager.OpenExeConfiguration(assemblyPath);
ConnectionStringsSection csSection = config.ConnectionStrings;
Assembly Path can be gotten several ways:
Inside Installer class implementation with:
this.Context.Parameters["assemblypath"].ToString();
or sometimes with reflection:
Assembly service = Assembly.GetAssembly(typeof(MyInstaller));
string assemblyPath = service.Location;
The problem is that when your installer runs, you are still in installation phase and your application hasn't been fully installed. The app.config will only be available when the actual application is run.
You can however do the following:
Prompt the user for the username and password within the installer (or on the command line).
Pass this information to your installer class (google it)
Within your installer class, there is a variable that tells you the installation path
Within the appropriate event in the installer, use System.IO functions to open the app.config file and insert the user entered information
I had the same problem with a service installer. You have to call your config file "myService.exe.config" and use the OpenExeConfiguration method with the assembly path to look for the right config file (as it is explained in the first answer, when your installers run, the base directory is the directory of the installUtil and not your installer)
{
Assembly __ServiceAssembly = Assembly.GetAssembly(typeof(MyServiceInstaller));
Configuration config = ConfigurationManager.OpenExeConfiguration(__ServiceAssembly.Location);
KeyValueConfigurationCollection svcSettings = config.AppSettings.Settings;
info("Service name : " + svcSettings["ServiceName"].Value);
}
If you don't want to follow the "myService.exe.config" format, use an exeConfigurationFileMap:
{
Assembly __ServiceAssembly = Assembly.GetAssembly(typeof(SyslogServiceInstaller));
ExeConfigurationFileMap configFileMap = new ExeConfigurationFileMap();
configFileMap.ExeConfigFilename =
Path.Combine(Directory.GetParent(__ServiceAssembly.Location).ToString(),
"App.config");
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(
configFileMap, ConfigurationUserLevel.None);
KeyValueConfigurationCollection mySettings = config.AppSettings.Settings;
Console.Out.WriteLine(mySettings["ServiceName"].Value);
}
You really shouldn't store a password in an app.config file, that is very bad. You need to either use the service account, the current user or prompt them. Also a user can right click an .exe (which presumably is what is triggering your install) and select "run as" to change their credentials before installation (in which case current user would be a good selection).
Additionally in the services manager a user can change which user the service is supposed to run as after the installation is over. But you definitely don't want to store passwords in plain text files.