I know this is an old thread, but it was very useful. I have the exact same situation as Cade Roux, as I wanted /netonly style functionality.
John Rasch's answer works with one small modification!!!
Add the following constant (around line 102 for consistency):
private const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
Then change the call to LogonUser
to use LOGON32_LOGON_NEW_CREDENTIALS
instead of LOGON32_LOGON_INTERACTIVE
.
That's the only change I had to make to get this to work perfectly!!! Thank you John and Cade!!!
Here's the modified code in full for ease of copy/pasting:
namespace Tools
{
#region Using directives.
// ----------------------------------------------------------------------
using System;
using System.Security.Principal;
using System.Runtime.InteropServices;
using System.ComponentModel;
// ----------------------------------------------------------------------
#endregion
/////////////////////////////////////////////////////////////////////////
/// <summary>
/// Impersonation of a user. Allows to execute code under another
/// user context.
/// Please note that the account that instantiates the Impersonator class
/// needs to have the 'Act as part of operating system' privilege set.
/// </summary>
/// <remarks>
/// This class is based on the information in the Microsoft knowledge base
/// article http://support.microsoft.com/default.aspx?scid=kb;en-us;Q306158
///
/// Encapsulate an instance into a using-directive like e.g.:
///
/// ...
/// using ( new Impersonator( "myUsername", "myDomainname", "myPassword" ) )
/// {
/// ...
/// [code that executes under the new context]
/// ...
/// }
/// ...
///
/// Please contact the author Uwe Keim (mailto:uwe.keim@zeta-software.de)
/// for questions regarding this class.
/// </remarks>
public class Impersonator :
IDisposable
{
#region Public methods.
// ------------------------------------------------------------------
/// <summary>
/// Constructor. Starts the impersonation with the given credentials.
/// Please note that the account that instantiates the Impersonator class
/// needs to have the 'Act as part of operating system' privilege set.
/// </summary>
/// <param name="userName">The name of the user to act as.</param>
/// <param name="domainName">The domain name of the user to act as.</param>
/// <param name="password">The password of the user to act as.</param>
public Impersonator(
string userName,
string domainName,
string password)
{
ImpersonateValidUser(userName, domainName, password);
}
// ------------------------------------------------------------------
#endregion
#region IDisposable member.
// ------------------------------------------------------------------
public void Dispose()
{
UndoImpersonation();
}
// ------------------------------------------------------------------
#endregion
#region P/Invoke.
// ------------------------------------------------------------------
[DllImport("advapi32.dll", SetLastError = true)]
private static extern int LogonUser(
string lpszUserName,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int DuplicateToken(
IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool CloseHandle(
IntPtr handle);
private const int LOGON32_LOGON_INTERACTIVE = 2;
private const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
private const int LOGON32_PROVIDER_DEFAULT = 0;
// ------------------------------------------------------------------
#endregion
#region Private member.
// ------------------------------------------------------------------
/// <summary>
/// Does the actual impersonation.
/// </summary>
/// <param name="userName">The name of the user to act as.</param>
/// <param name="domainName">The domain name of the user to act as.</param>
/// <param name="password">The password of the user to act as.</param>
private void ImpersonateValidUser(
string userName,
string domain,
string password)
{
WindowsIdentity tempWindowsIdentity = null;
IntPtr token = IntPtr.Zero;
IntPtr tokenDuplicate = IntPtr.Zero;
try
{
if (RevertToSelf())
{
if (LogonUser(
userName,
domain,
password,
LOGON32_LOGON_NEW_CREDENTIALS,
LOGON32_PROVIDER_DEFAULT,
ref token) != 0)
{
if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
{
tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
impersonationContext = tempWindowsIdentity.Impersonate();
}
else
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
else
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
else
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
finally
{
if (token != IntPtr.Zero)
{
CloseHandle(token);
}
if (tokenDuplicate != IntPtr.Zero)
{
CloseHandle(tokenDuplicate);
}
}
}
/// <summary>
/// Reverts the impersonation.
/// </summary>
private void UndoImpersonation()
{
if (impersonationContext != null)
{
impersonationContext.Undo();
}
}
private WindowsImpersonationContext impersonationContext = null;
// ------------------------------------------------------------------
#endregion
}
/////////////////////////////////////////////////////////////////////////
}
The problem with Get-Credential
is that it will always prompt for a password. There is a way around this however but it involves storing the password as a secure string on the filesystem.
The following article explains how this works:
Using PSCredentials without a prompt
In summary, you create a file to store your password (as an encrypted string). The following line will prompt for a password then store it in c:\mysecurestring.txt
as an encrypted string. You only need to do this once:
read-host -assecurestring | convertfrom-securestring | out-file C:\mysecurestring.txt
Wherever you see a -Credential
argument on a PowerShell command then it means you can pass a PSCredential
. So in your case:
$username = "domain01\admin01"
$password = Get-Content 'C:\mysecurestring.txt' | ConvertTo-SecureString
$cred = new-object -typename System.Management.Automation.PSCredential `
-argumentlist $username, $password
$serverNameOrIp = "192.168.1.1"
Restart-Computer -ComputerName $serverNameOrIp `
-Authentication default `
-Credential $cred
<any other parameters relevant to you>
You may need a different -Authentication
switch value because I don't know your environment.
Best Answer
Once a security token has been created from credentials entered and validated against active directory, the password is no longer kept around. It is not available for retrieval and reuse elsewhere. Only the token remains. This is by design.UPDATE:
I dug a little further to bolster my case, and it's not quite as above but the end result is the same. The password used with
runas.exe
does not appear to be available. The network credentials are not validated against AD, which makes sense in retrospect since you often use/netonly
for working with a remote, untrusted domain: By definition, you cannot validate the remote credentials from the local system. From MSDN:Here's information for the flag
LOGON_NETCREDENTIALS_ONLY
, used withCreateProcessWithLogonW
.Ok, so since it can't validate the credentials and get a token, then it must store the password somewhere in memory since it must pass them over the wire later for SSPI etc. So, can we get at them from the process launched from
runas.exe
? Let's see:I literally used
foo\bar
for domain and user above. It is not validated, as mentioned already. I entered12345
for a password. The above line will launch a new instance of powershell. So, from that new instance, let's look at the default network credentials:Oh well, out of luck: Nothing there. My guess is the credentials are guarded in some encrypted part of memory in the kernel, probably the LSA (local security authority) out of reach from prying processes.