Start a Process from a C# Application impersonating a specific User - c#

I have a .NET c# application,
the business flow (short and to the point) is:
Users make a call to my app which authenticates them by windows authentication.
If the user is a "special user" (business logic part, E.g. some account admin), I impersonate to a "Master Account" in the active directory which has read / write permissions to a shared folder.
I then create folders and files with the impersonated user context --> This works.
But when I try to start a process (bcp.exe for those who care), I can't get it to work!
After many failing attempts , getting many error messages such as "access denied",
and trying to use almost all of the Process.ProcessStartInfo() attributes which should assist me to run a process as a different user, I decided to Post this as a question.
I've read many blogs suggesting the only way to do this is to use the win32 dll and call CreateProcessAsUser() method, but it's just to damn complicated, and I couldn't find any working sample of it.
bottom line question:
How can I start a Process (Process.Start) from a c# app while in impersonation context as the impersonated user?
My code:
private void ExecuteCommand(string backupSource, string backupFilename, string formatFilename)
{
// This works --> Here I'm under impersonated user context
// with read write permissions to the shared folder
if (!Directory.Exists(OutputPath))
{
Directory.CreateDirectory(OutputPath);
}
using (Process p = new Process())
{
p.StartInfo = GetProcessStartInfo(backupSource, backupFilename, formatFilename);
//Here I'm currently getting ""Access Denied" exception"
p.Start();
...
}
}
private ProcessStartInfo GetProcessStartInfo(string backupSource, string backupFilename, string formatFilename)
{
var result = new ProcessStartInfo();
result.UseShellExecute = false;
result.RedirectStandardOutput = true;
result.RedirectStandardError = true;
var file = Path.Combine(PathToExecutable, "bcp.exe");
// #"C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\110\Tools\Binn\bcp.exe";
result.FileName = file;
result.WorkingDirectory = Path.GetDirectoryName(file);
result.LoadUserProfile = true;
result.Domain = "IMPERSONATED USER DOMAIN";
result.UserName = "IMPERSONATED USER NAME";
var ssPwd = new SecureString();
string password = "IMPERSONATED USER PASSWORD";
for (int x = 0; x < password.Length; x++)
{
ssPwd.AppendChar(password[x]);
}
result.Password = ssPwd;
var backupFullFilename = GetFullFileName(backupFilename);
StringBuilder arguments = new StringBuilder(backupSource);
var formatFullFilename = GetFullFileName(formatFilename);
FormatArguments(arguments, backupFullFilename, formatFullFilename);
var argumentsString = arguments.ToString();
result.Arguments = argumentsString;
return result;
}
Edit #1:
I was able to resolve the "Access is denied" exception, by adding the impersonating user to the administrators group on the machine which the application that starts the process runs on.
Now, I'm having a different issue, no exception but seems like the process isn't starting, or exiting right on start, I'm getting exit code 1073741502.
I've read I must use the native win32 api CreateProcessAsUser() instead of System.Diagnostics.Process.Start() but I'm not sure if that's true.
Ideas?
Assistance would be appreciated.

Related

How to uninstall a UWP application programmatically without admin access c#

I have sideloaded a UWP application onto my clients machine.
I would now like to uninstall the program, but without admin access.
I have found Remove-AppxPackage but this uses powershell and so would need an executionpolicy set which would require admin access
For my WPF applications I would just delete the directory containing the application but with a UWP app I'm not even sure what to delete.
Essentially I would like to programatically click the uninstall button on from the Add and remove programs
I did look at this link How to uninstall application programmatically with the code:
public static string GetUninstallCommandFor(string productDisplayName)
{
RegistryKey localMachine = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine,RegistryView.Registry64);
string productsRoot = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products";
RegistryKey products = localMachine.OpenSubKey(productsRoot);
string[] productFolders = products.GetSubKeyNames();
foreach (string p in productFolders)
{
RegistryKey installProperties = products.OpenSubKey(p + #"\InstallProperties");
if (installProperties != null)
{
string displayName = (string)installProperties.GetValue("DisplayName");
Debug.WriteLine(displayName);
if ((displayName != null) && (displayName.Contains(productDisplayName)))
{
string uninstallCommand = (string)installProperties.GetValue("UninstallString");
return uninstallCommand;
}
}
}
return "";
}
But this didn't find my application - eventhough it is in the "Apps and features" settings page
Ok my solution as advised by Nico Zhu was to use powershell. I created a method like so:
private static void LaunchProcess(string uri, string args)
{
var psi = new ProcessStartInfo();
psi.UseShellExecute = true;
psi.CreateNoWindow = false;
psi.Arguments = args;
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.FileName = uri;
var proc = Process.Start(psi);
proc.WaitForExit();
var exitcode = proc.ExitCode;
}
and used it like so:
LaunchProcess("powershell.exe", "get-appxpackage *AppPackageNameThatOnlyMatchesYourAppPackage* | remove-appxpackage");
This process surprisingly didn't require admin rights.
I must say though from a microsoft developer point of view UX. For managing distribution of my UWP apps, this is another thumbs down for UWP vs WPF

Authentication user to web application from desktop application

I am a novice programmer. I need write desktop application, where user enters the username and password. After clicked on button calls the method Start class Process. Unfortunately, appears in an error message System.ComponentModel.Win32Exception, username or password is incorrect.
My code:
//Download data
String user = this.textBoxUser.Text;
String pass = this.textBoxPassword.Text;
//Password
SecureString secret = SecureStringConverter.ConvertToSecureString(pass);
//Webbrowser
string file = "chrome.exe";
string domain = #"http://localhost:62074/";
//Process start
Process proc = new Process();
Microsoft.Win32.RegistryKey subKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(#"http\shell\open\command");
String DefaultBrowser = subKey.GetValue(null).ToString();
if (DefaultBrowser != null)
{
int startIndex = DefaultBrowser.IndexOf("\"") + 1;
int endIndex = DefaultBrowser.IndexOf("\"", startIndex);
string RegDefaultBrowserPath = DefaultBrowser.Substring(startIndex, endIndex - startIndex);
proc.StartInfo.FileName = RegDefaultBrowserPath;
proc.StartInfo.Arguments = domain;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.LoadUserProfile = false;
proc.StartInfo.UserName = user;
proc.StartInfo.Password = secret;
proc.Start();
}
And this method to convert string to SecureString
public static class SecureStringConverter
{
public static SecureString ConvertToSecureString(string password)
{
if (password == null)
throw new ArgumentNullException("password");
unsafe
{
fixed (char* passwordChars = password)
{
var securePassword = new SecureString(passwordChars, password.Length);
securePassword.MakeReadOnly();
return securePassword;
}
}
}
}
I don't think your approach will work. At least not as simple as you might expect it to happen. You may get it working by launching Internet Explorer with explicit credentials to authenticate against applications hosted in IIS, as they may be configured to use domain account for authentication. This scenario essentially is for Intranet web applications. With Crome it is a completely different story.
Merits of your approach are completely not obvious. It is expected that credentials of user logged in to Windows are used for authentication. With your idea, it sounds more like someone else signs in to Windows for me, and I use my own credentials to run web app, but still use remaining windows apps with someone else's credentials.
The following article should give you an idea that it will depend on infrastructure support, and specific sign in process, not just as simple as forwarding explicit credentials to get it working with external web applications who knows nothing about your domain:
https://support.google.com/a/answer/60224?hl=en

Delete a directory where someone has opened a file

I am trying to programmatically delete and replace the contents of an application, "App A", using an "installer" program, which is just a custom WPF .exe app, we'll call "App B". (My question concerns code in "App B".)
GUI Setup (not particularly important)
App B has a GUI where a user can pick computer names to copy App A onto. A file picker is there the admin uses to fill in the source directory path on the local machine by clicking "App A.exe". There are also textboxes for a user name and password, so the admin can enter their credentials for the target file server where App A will be served - the code impersonates the user with these to prevent permission issues. A "Copy" button starts the routine.
Killing App A, File Processes, and Doing File Deletion
The Copy routine starts by killing the "App A.exe" process on all computers in the domain, as well as explorer.exe, in case they had App A's explorer folder open. Obviously this would be done afterhours, but someone may still have left things open and locked their machine before going home. And that's really the base of the problem I'm looking to solve.
Prior to copying over the updated files, we want to delete the entire old directory. In order to delete the directory (and its subdirectories), each file within them has to be deleted. But say they had a file open from App A's folder. The code finds any locking process on any file prior to deleting it (using code from Eric J.'s answer at How do I find out which process is locking a file using .NET? ), it kills that process on whatever computer it is running on. If local, it just uses:
public static void localProcessKill(string processName)
{
foreach (Process p in Process.GetProcessesByName(processName))
{
p.Kill();
}
}
If remote, it uses WMI:
public static void remoteProcessKill(string computerName, string fullUserName, string pword, string processName)
{
var connectoptions = new ConnectionOptions();
connectoptions.Username = fullUserName; // #"YourDomainName\UserName";
connectoptions.Password = pword;
ManagementScope scope = new ManagementScope(#"\\" + computerName + #"\root\cimv2", connectoptions);
// WMI query
var query = new SelectQuery("select * from Win32_process where name = '" + processName + "'");
using (var searcher = new ManagementObjectSearcher(scope, query))
{
foreach (ManagementObject process in searcher.Get())
{
process.InvokeMethod("Terminate", null);
process.Dispose();
}
}
}
Then it can delete the file. All is well.
Directory Deletion Failure
In my code below, it is doing the recursive deletion of the files, and does it fine, up until the Directory.Delete(), where it will say The process cannot access the file '\\\\SERVER\\C$\\APP_A_DIR' because it is being used by another process, because I am attempting to delete the directory while I had a file still open from it (even though the code was actually able to delete the physical file-the instance is still open).
public void DeleteDirectory(string target_dir)
{
string[] files = Directory.GetFiles(target_dir);
string[] dirs = Directory.GetDirectories(target_dir);
List<Process> lstProcs = new List<Process>();
foreach (string file in files)
{
File.SetAttributes(file, FileAttributes.Normal);
lstProcs = ProcessHandler.WhoIsLocking(file);
if (lstProcs.Count == 0)
File.Delete(file);
else // deal with the file lock
{
foreach (Process p in lstProcs)
{
if (p.MachineName == ".")
ProcessHandler.localProcessKill(p.ProcessName);
else
ProcessHandler.remoteProcessKill(p.MachineName, txtUserName.Text, txtPassword.Password, p.ProcessName);
}
File.Delete(file);
}
}
foreach (string dir in dirs)
{
DeleteDirectory(dir);
}
//ProcessStartInfo psi = new ProcessStartInfo();
//psi.Arguments = "/C choice /C Y /N /D Y /T 1 & Del " + target_dir;
//psi.WindowStyle = ProcessWindowStyle.Hidden;
//psi.CreateNoWindow = true;
//psi.FileName = "cmd.exe";
//Process.Start(psi);
//ProcessStartInfo psi = new ProcessStartInfo();
//psi.Arguments = "/C RMDIR /S /Q " + target_dir;
//psi.WindowStyle = ProcessWindowStyle.Hidden;
//psi.CreateNoWindow = true;
//psi.FileName = "cmd.exe";
//Process.Start(psi);
// This is where the failure occurs
//FileSystem.DeleteDirectory(target_dir, DeleteDirectoryOption.DeleteAllContents);
Directory.Delete(target_dir, false);
}
I've left things I've tried commented out in the code above. While I can kill processes attached to the files and delete them, is there a way to kill processes attached to folders, in order to delete them?
Everything online I saw tries to solve this using a loop-check with a delay. This will not work here. I need to kill the file that was opened-which I do-but also ensure the handle is released from the folder so it can also be deleted, at the end. Is there a way to do this?
Another option I considered that will not work:
I thought I might just freeze the "installation" (copying) process by marking that network folder for deletion in the registry and schedule a programmatic reboot of the file server, then re-run afterwards. How to delete Thumbs.db (it is being used by another process) gives this code by which to do this:
[DllImport("kernel32.dll")]
public static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, int dwFlags);
public const int MOVEFILE_DELAY_UNTIL_REBOOT = 0x4;
//Usage:
MoveFileEx(fileName, null, MOVEFILE_DELAY_UNTIL_REBOOT);
But it has in the documentation that If MOVEFILE_DELAY_UNTIL_REBOOT is used, "the file cannot exist on a remote share, because delayed operations are performed before the network is available." And that was assuming it might have allowed a folder path, instead of a file name. (Reference: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365240(v=vs.85).aspx ).
So there are 2 scenarios I wanted to handle - both are where the folder is prevented from being deleted:
1) A user has a file open on their local machine from the application's folder on the file server.
2) An admin has a file open from the application's folder, which they will see while remoted (RDP'ed) into the server.
I've settled on a way forward. If I run into this issue, I figure about all I can do is to either:
1) Freeze the "installation" (copying) process by simply scheduling a programmatic reboot of the file server in the IOException block if I really want to blow away the folder (not ideal and probably overkill, but others running across this same issue may be inspired by this option). The installer will need to be run again to copy the files after the server reboots.
[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);
LogonUser(userName, domainName, password,
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
out safeTokenHandle);
try
{
using (WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle()))
{
using (WindowsImpersonationContext impersonatedUser = newId.Impersonate())
{
foreach (Computer pc in selectedList) // selectedList is an ObservableCollection<Computer>
{
string newDir = "//" + pc.Name + txtExtension.Text; // the textbox has /C$/APP_A_DIR in it
if (Directory.Exists(newDir))
{
DeleteDirectory(newDir); // <-- this is where the exception happens
}
}
}
}
}
catch (IOException ex)
{
string msg = "There was a file left open, thereby preventing a full deletion of the previous folder, though all contents have been removed. Do you wish to proceed with installation, or reboot the server and begin again, in order to remove and replace the installation directory?";
MessageBoxResult result = MessageBox.Show(msg, "Reboot File Server?", MessageBoxButton.OKCancel);
if (result == MessageBoxResult.OK)
{
var psi = new ProcessStartInfo("shutdown","/s /t 0");
psi.CreateNoWindow = true;
psi.UseShellExecute = false;
Process.Start(psi);
}
else
{
MessageBox.Show("Copying files...");
FileSystem.CopyDirectory(sourcePath, newDir);
MessageBox.Show("Completed!");
}
}
Reference: How to shut down the computer from C#
OR
2) Ignore it altogether and perform my copy, anyway. The files actually do delete, and I found there's really no problem with having a folder I can't delete, as long as I can write to it, which I can. So this is the one I ultimately picked.
So again, in the IOException catch block:
catch (IOException ex)
{
if (ex.Message.Contains("The process cannot access the file") &&
ex.Message.Contains("because it is being used by another process") )
{
MessageBox.Show("Copying files...");
FileSystem.CopyDirectory(sourcePath, newDir);
MessageBox.Show("Completed!");
}
else
{
string err = "Issue when performing file copy: " + ex.Message;
MessageBox.Show(err);
}
}
Code above leaves out my model for Computer, which just has a Name node in it, and the rest of my Impersonation class, which is based on my own rendition of several different (but similar) code blocks of how they say to do it. If anyone needs that, here are a couple of links to some good answers:
Need Impersonation when accessing shared network drive
copy files with authentication in c#
Related: Cannot delete directory with Directory.Delete(path, true)

After resetting user's password in Active Directory using C#, both old and new passwords are working

Below is the code which is being used to reset the password. I want to stop this behavior. Only new password should work. user should not be able to log in with old password.
using (var search= new DirectorySearcher(dir))
{
search.Asynchronous = false;
search.CacheResults = false;
dirSearch.Filter = "(&(objectCategory=User)(objectClass=person)(name=" + UserName.Trim() + "))";
SearchResult result = dirSearch.FindOne();
if (result != null)
{
using (var entryUpdate = result.GetDirectoryEntry())
{
entryUpdate.Invoke("setpassword", new object[] { NewPassword });
entryUpdate.CommitChanges();
//entryUpdate.RefreshCache();
}
}
result = null;
}
It's only possible to have two different passwords at the same time when Active Directory replication is broken. This is not actually a code issue. The way to fix it is to determine where the AD replication is broken. You can quickly check AD Health at a glance by running the command repadmin /showrepl. If you see errors, then run dcdiag /v on any domain controllers showing errors in the output. A new favorite tool of mine now to determine AD Health also is to run the PowerShell utility ADHealthCheck.

ActiveDirectory Local Machine Account management - C#

I posted a question re LDAP account management, but after exploring this, it's not what i'm after. I've managed to find two ways of creating users on a machine, and i find one is much neater than the other, however, i am uncertain how to convert the first option over to the second option entirely.
This was my first solution:
Process MyProc = new Process();
MyProc.StartInfo.WorkingDirectory = System.Environment.SystemDirectory;
MyProc.StartInfo.FileName = "net.exe";
MyProc.StartInfo.UseShellExecute = false;
MyProc.StartInfo.RedirectStandardError = true;
MyProc.StartInfo.RedirectStandardInput = true;
MyProc.StartInfo.RedirectStandardOutput = true;
MyProc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
MyProc.StartInfo.Arguments = string.Format(#" user {0} {1} /ADD /ACTIVE:YES /EXPIRES:NEVER /FULLNAME:{0}"" /PASSWORDCHG:NO /PASSWORDREQ:YES", username, password);
MyProc.Start();
MyProc.WaitForExit();
int exit = MyProc.ExitCode;
MyProc.Close();
return exit == 0;
And this was my second (preffered) solution:
DirectoryEntry root = GetDELocalRoot();
DirectoryEntry user = root.Children.Add(username, "user");
//TODO: Always Active
//TODO: Never Expires
//TODO: No Password Change
//TODO: Password Required
user.Properties["description"].Value = "Account for running the MicaService and handling updates.";
user.Invoke("SetPassword", new object[] { password });
user.CommitChanges();
user.Close();
I would like to map all the settings in my TODO: from the first solution into my second neater solution.
I have tried the following line as well:
user.Properties["userAccountControl"].Value = ADS_USER_FLAG.ADS_UF_NORMAL_ACCOUNT | ADS_USER_FLAG.ADS_UF_PASSWD_CANT_CHANGE | ADS_USER_FLAG.ADS_UF_DONT_EXPIRE_PASSWD;
But this does not work as the property does not exist in cache.
NOTE: the GetDELocalRoot() = return new DirectoryEntry("WinNT://" + Environment.MachineName);
Thanks for any input!
Regards
Tris
Check out my friend Richard Mueller's web site which has lots of useful information and reference material on what those two providers - WinNT for local machine accounts vs. LDAP for network accounts - have to offer.
There's also a Excel sheeet with all attributes that the WinNT provider exposes - it's a lot less than what the LDAP provider has, so I'm not sure if you'll be able to set all the properties you're looking for.
Marc

Categories