I'm working with LibGit2Sharp to add a number of Git operations to an application. I've added the Microsoft.Alm.Authentication to help with Authentication and credential manager access. It works great for retrieving credentials that are already entered from the command line.
However is there any way to also hook into the Credential Manager's Login UI that prompts for username and password for Github, BitBucket and VSTS. This UI pops up automatically from the command line, but doesn't fire when using LibGit2Sharp.
I've looked at the GitCredentialManager project on Github and I can see the components that provide the UI, but before trying to figure out how to hook those in explicitly, is there some way I'm missing that this is provided as part of the Microsoft.Alm.Authentication (or related package)? Or can anybody point to an example or guidance on how to best hook this up?
I managed to get your solution working with some minor modifications. I paste here a sample code to push using git credentials. It works using the credentials already stored in the computer and prompts for credentials the first time with UI.
Only problem I'm having so far is when user is prompt for credentials and they enter an invalid user/password. Git writes to the console asking for user/pass and the process is not finished until you input that. Tried to monitor StandardError/Output with no success. I get the error text in stderror but only after filling that manually.
public void PushLibGit2Sharp(string repositoryFolder, string branch)
{
using (var repo = new Repository(repositoryFolder))
{
var options = new PushOptions
{
CredentialsProvider = (url, usernameFromUrl, types) =>
{
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "git.exe",
Arguments = "credential fill",
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
Process process = new Process
{
StartInfo = startInfo
};
process.Start();
// Write query to stdin.
// For stdin to work we need to send \n instead of WriteLine
// We need to send empty line at the end
var uri = new Uri(url);
process.StandardInput.NewLine = "\n";
process.StandardInput.WriteLine($"protocol={uri.Scheme}");
process.StandardInput.WriteLine($"host={uri.Host}");
process.StandardInput.WriteLine($"path={uri.AbsolutePath}");
process.StandardInput.WriteLine();
// Get user/pass from stdout
string username = null;
string password = null;
string line;
while ((line = process.StandardOutput.ReadLine()) != null)
{
string[] details = line.Split('=');
if (details[0] == "username")
{
username = details[1];
}
else if (details[0] == "password")
{
password = details[1];
}
}
return new UsernamePasswordCredentials()
{
Username = username,
Password = password
};
}
};
repo.Network.Push(repo.Branches[branch], options);
}
}
Unfortunately, there's no functionality in libgit2 (or LibGit2Sharp) to talk directly to the git-credential-helper functionality, which is what git itself uses to perform this action.
Instead, you can set a CredentialsHandler on your PushOptions (or FetchOptions), eg:
options.CredentialsProvider = (url, usernameFromUrl, types) => {
string username, password;
Uri uri = new Uri(url);
string hostname = uri.Host;
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.UseShellExecute = false;
startInfo.RedirectStandardInput = true;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
startInfo.FileName = "git.exe";
startInfo.Arguments = "credential fill";
Process process = new Process();
process.StartInfo = startInfo;
process.Start();
process.StandardInput.WriteLine("hostname={0}", hostname);
process.StandardInput.WriteLine("username={0}", usernameFromUrl);
while ((line = process.StandardOutput.ReadLine()) != null)
{
string[] details = line.Split('=', 2);
if (details[0] == "username")
{
username = details[1];
}
else if (details[0] == "password")
{
password = details[1];
}
}
return new UsernamePasswordCredentials(username, password);
};
Related
I am trying to create an application which will install the msi from the c# windows application, here i wanna take the input from user for UserName, Domain and password so that i can run the application in that user account. in the below code if i only give startInfo.Verb = "runas" its working but i want to provide the user name and password of admin and run it. can you guyz help me out.
private void InstallProbe()
{
try
{
bool gdfg= IsRunAsAdmin();
//processObj.InitializeProcess(txtUserName.Text, txtPassword.Text);
string installcmd = "/c msiexec /i \"{0}\" /quiet TARGETDIR=\"{1}\" HOST=\"{2}\" PORT=\"{3}\" USEHTTPS=\"{4}\" STEALTHMODE=\"{5}\" ORGCODE=\"{6}\"";
installcmd = string.Format(installcmd, txtFilePath.Text, #"%PROGRAMFILES%\ProHance Mate", "services.jamochatech.com", "8080", false, 0, "PHSALE");
string uname, domain = string.Empty;
//RunCommand("cmd", installcmd, processObj);
if (txtUserName.Text.IndexOf("\\") > 0)
{
string[] strarr = txtUserName.Text.Split('\\');
uname = strarr[1];
domain = strarr[0];
}
else
{
uname = txtUserName.Text;
domain = ".";
}
Process process = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo();
//startInfo.Verb = "runas";
startInfo.Domain = domain;
startInfo.UserName = uname;
startInfo.Password = ToSecureString(txtPassword.Text);
startInfo.FileName = "cmd";
startInfo.Arguments = installcmd;
startInfo.CreateNoWindow = true;
startInfo.UseShellExecute = false;
startInfo.LoadUserProfile = true;
MessageBox.Show(installcmd);
process.StartInfo = startInfo;
process.Start();
process.WaitForExit(60000);
}
catch (Exception ex)
{
MessageBox.Show("Exception occured while installing the ProHance Mate " + ex.Message);
}
}
Disregarding the MSI context, you are simply trying to launch a new process (msiexec.exe) under a specific user context. Check the thread below and others alike.
In Windows: How do you programatically launch a process in administrator mode under another user context?
I'm trying to do a web api which allows my to start/stop/reset my application pools and sites inside IIS.
Currently, I have tested that running administration level Visual studio allows me to run my code flawlessly. But once I'm not running visual studio with administration level privileged, or even deployed to IIS and run as a test service, my method inside breaks.
A little overview of how it is done is I get a Process to run CMD which utilizes the appcmd to list and reset the iis connections.
private List<SiteModels> GetSiteModels()
{
Process process = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo
{
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Minimized,
FileName = "cmd.exe",
WorkingDirectory = #"C:\",
Arguments = "/K appcmd list sites & exit",
Verb = "runas",
RedirectStandardOutput = true
};
process.StartInfo = startInfo;
process.EnableRaisingEvents = false;
List<SiteModels> sitesModelList = new List<SiteModels>();
string tempString = process.StandardOutput.ReadToEnd();
process.WaitForExit();
foreach (var line in tempString.Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
{
SiteModels sitesModel = new SiteModels();
string pattern = "SITE \"(?<SiteName>.*?)\"[\\s\\S]*?id:(?<Id>.*?),[\\s\\S]*?bindings:(?<Bindings>.*?),[\\s\\S]*?state:(?<State>.*?)\\)";
Regex regexPattern = new Regex(pattern);
Match match = regexPattern.Match(line);
if (match.Success)
{
sitesModel.SiteName = match.Groups["SiteName"].Value.Trim();
sitesModel.Id = int.Parse(match.Groups["Id"].Value.Trim());
sitesModel.Bindings = match.Groups["Bindings"].Value.Trim();
sitesModel.State = match.Groups["State"].Value.Trim();
sitesModelList.Add(sitesModel);
}
}
return sitesModelList;
}
I'm migrating batch script to .Net core and I'm trying to open another terminal from current terminal and run a command (I don't need stderr o stout).
With batch only needs this command: start cmd /K gulp. I'm trying to do the same with .Net core but only found the way to run the command inside current terminal.
private static string Run (){
var result = "";
try
{
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = "cmd.exe";
startInfo.Arguments = $"/c \"gulp browserSync\"";
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
using (Process process = Process.Start(startInfo))
{
result = process.StandardError.ReadToEnd();
process.WaitForExit();
}
}
catch (Exception Ex)
{
Console.WriteLine(Ex.Message);
Console.ReadKey();
}
return result;
}
I'm trying changing this properties in order to open in another terminal:
startInfo.RedirectStandardOutput = false;
startInfo.RedirectStandardError = false;
startInfo.UseShellExecute = true;
But make an exception:
UseShellExecute must always be set to false.
From the MSDN docs:
UseShellExecute must be false if the UserName property is not null or an empty string, or an InvalidOperationException will be thrown when the Process.Start(ProcessStartInfo) method is called.
startInfo.UserName = null;
edit: I'm not sure why you have to pass in the arguments, but if all you want is a new CMD window try this:
try
{
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
WorkingDirectory = #"C:/users/replace/where_gulp_is_located",
Arguments = #"/c gulp", // add /K if its required, I don't know if its for gulp for to open a new cmd window
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
};
Process proc = new Process();
proc.StartInfo = startInfo;
proc.Start();
if (showOut)
{ ///code }
}catch(Exception ex)
{
Console.WriteLine(ex);
}
You wont need startInfo.UserName in this case because you are specifying a working directory.
Thanks to #bender-bending answer I found a way to solve it. Due security limitations need user/password credentials in order to autorice current terminal to open a new one.
WorkingDirectory, user, password and domain are required.
Create no window, redirect output and redirect error must be false, in order to see command result in new window.
public static void Sample(){
try
{
Console.Write("Password: ");
StringBuilder password = new StringBuilder();
while (true)
{
var key = System.Console.ReadKey(true);
if (key.Key == ConsoleKey.Enter) break;
password.Append(key.KeyChar);
}
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
WorkingDirectory = "C:/path_to/Gulp",
Arguments = $"/c \"gulp browserSync\"",
UseShellExecute = false,
RedirectStandardOutput = false,
RedirectStandardError = false,
UserName = Machine.User(),
PasswordInClearText = password.ToString(),
Domain = Machine.Domain(),
CreateNoWindow = false
};
Process proc = new Process();
proc.StartInfo = startInfo;
proc.Start();
//proc.WaitForExit();
} catch (Exception ex)
{
System.Console.WriteLine(ex);
System.Console.ReadKey();
}
}
.Net Core doesn't have a method to obtain user and domain. We can use this class to get this values from environment variables.
public static class Machine
{
public static string User(){
return Environment.GetEnvironmentVariable("USERNAME") ?? Environment.GetEnvironmentVariable("USER");
}
public static string Domain(){
return Environment.GetEnvironmentVariable("USERDOMAIN") ?? Environment.GetEnvironmentVariable("HOSTNAME");
}
}
Hope it helps!
I want to create a windows user using c# code. Below methods works fine when the logged in user is having Administratorive privilage. It is not working with the limited user. Note that I can pass the windows user name and password of the administrator user. Basically I want to impersonate. I tried impersonation it did not work. I tried passing user namd and password to the processinfo. I got the error "The stub received bad data". So can any one help me on how to create the windows user using c# code by impersonation.
public static void CreateUser(string userName, string password, string description, string adminUserName, string adminPassword)
{
Process process = new Process();
ProcessStartInfo processInfo = new ProcessStartInfo();
processInfo.WorkingDirectory = Environment.SystemDirectory;
processInfo.FileName = "net.exe";
processInfo.UseShellExecute = false;
processInfo.RedirectStandardError = true;
processInfo.RedirectStandardInput = true;
processInfo.RedirectStandardOutput = true;
processInfo.WindowStyle = ProcessWindowStyle.Hidden;
processInfo.Arguments = #" user " + userName + #" " + password + #" /ADD /ACTIVE:YES " +
#"/EXPIRES:NEVER /FULLNAME:" + userName + #" /PASSWORDCHG:NO /PASSWORDREQ:YES";
if (!string.IsNullOrEmpty(adminUserName))
{
processInfo.UserName = adminUserName;
processInfo.Password = WindowsSecurityHelper.GetSecuredString(adminPassword);
}
process.StartInfo = processInfo;
process.Start();
process.WaitForExit();
process.Close();
}
or
public static void CreateUser(string userName, string password, string description, string userGroup = "Users")
{
PrincipalContext pc = new PrincipalContext(ContextType.Machine, null);
GroupPrincipal gp = GroupPrincipal.FindByIdentity(pc, userGroup);
if (gp != null)
{
UserPrincipal u = new UserPrincipal(pc);
u.SetPassword(password);
u.Name = userName;
u.Description = description;
u.UserCannotChangePassword = true;
u.PasswordNeverExpires = true;
u.Save();
gp.Members.Add(u);
gp.Save();
}
}
You can use the Process class with different credentials - instead of setting it directly, use ProcessStartInfo.
You can set a UserName and Password on the ProcessStartInfo class for the user you wish to execute as (Password is a SecureString, by the way) - pass this to the Process constructor and you are good to go.
ProcessStartInfo startInfo = new ProcessStartInfo("net.exe");
startInfo.UserName = Administrator;
startInfo.Password = ...;
...
Process.Start(startInfo);
I had the same error and found that I needed to specify the Domain parameter in the ProcessStartInfo.
I have an Windows Form application that supplies the User Name, Domain, and Password to the StartInfo, and it throws this:
System.ComponentModel.Win32Exception: The handle is invalid
at System.Diagnostics.Process.StartWithCreateProcess(ProcessStartInfo startInfo)
at System.Diagnostics.Process.Start()
When I allow the credentials to default to current user I get no such error, and the process I start works to the extent that it doesn't need to use credentials (the creds are necessary for mapping a drive in an MSBuild script). Here's the code that fills the start info:
Process p = new Process();
ProcessStartInfo si = new ProcessStartInfo(buildApp, buildArgs);
si.WorkingDirectory = msBuildWorkingDir;
si.UserName = txtUserName.Text;
char[] psw = txtPassword.Text.ToCharArray();
SecureString ss = new SecureString();
for (int x = 0; x < psw.Length; x++)
{
ss.AppendChar(psw[x]);
}
si.Password = ss;
si.Domain = "ABC";
si.RedirectStandardOutput = true;
si.UseShellExecute = false;
si.WorkingDirectory = txtWorkingDir.Text;
p.StartInfo = si;
p.Start();
It isn't that the user/psw isn't matching, because when I provide a bad psw, for example, it catches it. So, this "invalid handle" thing is happening after the cred is passed. Any ideas on what I might be omitting or screwing up?
You have to redirect your Input, Error, and Output.
for example:
ProcessStartInfo info = new ProcessStartInfo("cmd.exe");
info.UseShellExecute = false;
info.RedirectStandardInput = true;
info.RedirectStandardError = true;
info.RedirectStandardOutput = true;
info.UserName = dialog.User;
using (Process install = Process.Start(info)) {
string output = install.StandardOutput.ReadToEnd();
install.WaitForExit();
// Do something with you output data
Console.WriteLine(output);
}
Also microsoft has said the error should read, "Unable to redirect input." (used to have a link, but that no longer worked)