One of the best features of Windows is the Remote Desktop Connection software and one of the most annoying features is Windows' inability (or rather unwillingness) to use the current credential (username and password).
Yes, "Remember me" works. But it means having to type the password again and again for every server one visits and saving it for each server individually. The Remote Desktop client stores the username and password in the Windows Credential Manager.
It is possible to add a credential to the Credential Manager manually.
PS M:\> cmdkey /add:TERMSRV/telly /user:testuser /pass:password
CMDKEY: Credential added successfully.
PS M:\> cmdkey /list:TERMSRV/telly
Currently stored credentials for TERMSRV/telly:
Target: TERMSRV/telly
Type: Domain Password
User: testuser
Adding to the Credential Manager programmatically is similarly simple.
Using native code, that is.
#include <wincred.h>
BOOL ok;
int error;
int main()
{
// credential information
LPWSTR sTargetName = L"TERMSRV/telly";
LPWSTR sUserName = L"testuser";
LPWSTR sPassword = L"password";
DWORD cbCredentialBlobSize = (DWORD)(wcslen(sPassword) * sizeof(WCHAR));
// create credential
CREDENTIAL credential = { 0 };
credential.Type = CRED_TYPE_DOMAIN_PASSWORD; // 2
credential.TargetName = sTargetName;
credential.CredentialBlobSize = cbCredentialBlobSize;
credential.CredentialBlob = (LPBYTE)sPassword;
credential.Persist = CRED_PERSIST_ENTERPRISE; // 3
credential.UserName = sUserName;
// write credential to credential store
ok = CredWriteW(&credential, 0);
error = GetLastError();
wprintf(L"%d\n", error);
return error;
}
It is actually a lot more complicated using managed code. The CREDENTIAL struct has to be rebuilt in managed code and the CredWriteW() API declared. The code is much longer and it is very easy to make mistakes.
using System.Text;
using System.Runtime.InteropServices;
namespace CreateCredTestManaged
{
class Program
{
static bool ok;
static int error;
// credential struct definition as per Windows API
[StructLayout(LayoutKind.Sequential)]
struct CREDENTIAL
{
public uint Flags;
public uint Type;
public IntPtr TargetName;
public IntPtr Comment;
public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
public uint CredentialBlobSize;
public IntPtr CredentialBlob;
public uint Persist;
public uint AttributeCount;
public IntPtr Attributes;
public IntPtr TargetAlias;
public IntPtr UserName;
}
// CredWriteW from Windows API
[DllImport("advapi32")]
static extern bool CredWriteW(
ref CREDENTIAL Credential,
uint Flags
);
static void Main(string[] args)
{
// credential information
string sTargetName = "TERMSRV/telly";
string sUserName = "testuser";
string sPassword = "password";
uint cbCredentialBlobSize = (uint)Encoding.Unicode.GetByteCount(sPassword);
// create credential
CREDENTIAL credential = new CREDENTIAL {
Type = 2, // correponds to CRED_TYPE_DOMAIN_PASSWORD
TargetName = Marshal.StringToCoTaskMemUni(sTargetName), // copies managed into unmanaged memory
CredentialBlobSize = cbCredentialBlobSize,
CredentialBlob = Marshal.StringToCoTaskMemUni(sPassword),
Persist = 3, // corresponds to CRED_PERSIST_ENTERPRISE
UserName = Marshal.StringToCoTaskMemUni(sUserName)
};
ok = CredWriteW(ref credential, 0);
error = Marshal.GetLastWin32Error(); // corresponds to GetLastError()
Console.WriteLine(error.ToString());
Marshal.FreeCoTaskMem(credential.TargetName); // releases bytes allocated in unmanaged memory
Marshal.FreeCoTaskMem(credential.CredentialBlob);
Marshal.FreeCoTaskMem(credential.UserName);
Environment.Exit(error);
}
}//class
}//namespace
Both programs create a credential in the Credential Manager like the cmdkey command quoted before does. It took me hours figuring it out...