Library code snippets
Using the Credential Management API
- Wrapper Code
- Using the CredentialsDialog class
Wrapper Code
Following secure development best practice, an application should obtain and store user credentials securely. Microsoft has provided a means to accomplish this on the desktop: the unmanaged Credential Management API, which exposes the capability to provide a standard login experience for the user which is also secure. There are a number of benefits to utilising this:
- The code is maintained by Microsoft. I put this in the first because I think it is an important advantage.
- The user experience is consistent.
- The user credentials can be shared easily across applications.
- It's secure. It's well-tested as being secure. If a security flaw is discovered, you can be sure that Microsoft will fix it.
This is documented on MSDN: Credentials Management > Authentication Reference
While researching this, I discovered some excellent resources:
- [MSDN Technical Article] "Using Credential Management in Windows XP and Windows Server 2003"
- [MSDN Magazine] "App Lockdown: Defend Your Apps and Critical User Info with Defensive Coding Techniques" by Kenny Kerr who has also published a Supplement to the article that deals with web applications.
- [Blog] Don Kiely posted a simple VB.NET sample on "Don't Reinvent the Wheel: Use CredUIPromptForCredentials as a Login Window" .
- [Wiki] The .NET Developer's Guide to Windows Security has an item "How To Prompt For A Password" .
What I didn't find was a comprehensive code sample of a Generic dialog implementation, so I decided to put one together.
Before going any further, I should highlight that the Credential Management API is only available on Windows XP / Windows Server 2003 onwards.
I don't usually post such a large slice of code as this but I felt that it would be the clearest way to describe the class and also has the benefit that readers can cut & paste the entire sample easily. So here is my entire credential wrapper code:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace SecureCredentialsLibrary
{
/// <summary>Encapsulates dialog functionality from the Credential Management API.</summary>
public sealed class CredentialsDialog
{
/// <summary>The only valid bitmap height (in pixels) of a user-defined banner.</summary>
private const int ValidBannerHeight = 60;
/// <summary>The only valid bitmap width (in pixels) of a user-defined banner.</summary>
private const int ValidBannerWidth = 320;
/// <summary>Initializes a new instance of the <see cref="T:SecureCredentialsLibrary.CredentialsDialog"/> class
/// with the specified target.</summary>
/// <param name="target">The name of the target for the credentials, typically a server name.</param>
public CredentialsDialog(string target) : this(target, null)
{ }
/// <summary>Initializes a new instance of the <see cref="T:SecureCredentialsLibrary.CredentialsDialog"/> class
/// with the specified target and caption.</summary>
/// <param name="target">The name of the target for the credentials, typically a server name.</param>
/// <param name="caption">The caption of the dialog (null will cause a system default title to be used).</param>
public CredentialsDialog(string target, string caption) : this(target, caption, null)
{ }
/// <summary>Initializes a new instance of the <see cref="T:SecureCredentialsLibrary.CredentialsDialog"/> class
/// with the specified target, caption and message.</summary>
/// <param name="target">The name of the target for the credentials, typically a server name.</param>
/// <param name="caption">The caption of the dialog (null will cause a system default title to be used).</param>
/// <param name="message">The message of the dialog (null will cause a system default message to be used).</param>
public CredentialsDialog(string target, string caption, string message) : this(target, caption, message, null)
{ }
/// <summary>Initializes a new instance of the <see cref="T:SecureCredentialsLibrary.CredentialsDialog"/> class
/// with the specified target, caption, message and banner.</summary>
/// <param name="target">The name of the target for the credentials, typically a server name.</param>
/// <param name="caption">The caption of the dialog (null will cause a system default title to be used).</param>
/// <param name="message">The message of the dialog (null will cause a system default message to be used).</param>
/// <param name="banner">The image to display on the dialog (null will cause a system default image to be used).</param>
public CredentialsDialog(string target, string caption, string message, Image banner)
{
this.Target = target;
this.Caption = caption;
this.Message = message;
this.Banner = banner;
}
private bool _alwaysDisplay = false;
/// <summary>
/// Gets or sets if the dialog will be shown even if the credentials
/// can be returned from an existing credential in the credential manager.
/// </summary>
public bool AlwaysDisplay
{
get
{
return _alwaysDisplay;
}
set
{
_alwaysDisplay = value;
}
}
private bool _excludeCertificates = true;
/// <summary>Gets or sets if the dialog is populated with name/password only.</summary>
public bool ExcludeCertificates
{
get
{
return _excludeCertificates;
}
set
{
_excludeCertificates = value;
}
}
private bool _persist = true;
/// <summary>Gets or sets if the credentials are to be persisted in the credential manager.</summary>
public bool Persist
{
get
{
return _persist;
}
set
{
_persist = value;
}
}
private bool _keepName = false;
/// <summary>Gets or sets if the name is read-only.</summary>
public bool KeepName
{
get
{
return _keepName;
}
set
{
_keepName = value;
}
}
private string _name = String.Empty;
/// <summary>Gets or sets the name for the credentials.</summary>
public string Name
{
get
{
return _name;
}
set
{
if (value != null)
{
if (value.Length > CREDUI.MAX_USERNAME_LENGTH)
{
string message = String.Format(
Thread.CurrentThread.CurrentUICulture,
"The name has a maximum length of {0} characters.",
CREDUI.MAX_USERNAME_LENGTH);
throw new ArgumentException(message, "Name");
}
}
_name = value;
}
}
private string _password = String.Empty;
/// <summary>Gets or sets the password for the credentials.</summary>
public string Password
{
get
{
return _password;
}
set
{
if (value != null)
{
if (value.Length > CREDUI.MAX_PASSWORD_LENGTH)
{
string message = String.Format(
Thread.CurrentThread.CurrentUICulture,
"The password has a maximum length of {0} characters.",
CREDUI.MAX_PASSWORD_LENGTH);
throw new ArgumentException(message, "Password");
}
}
_password = value;
}
}
private bool _saveChecked = false;
/// <summary>Gets or sets if the save checkbox status.</summary>
public bool SaveChecked
{
get
{
return _saveChecked;
}
set
{
_saveChecked = value;
}
}
private bool _saveDisplayed = true;
/// <summary>Gets or sets if the save checkbox is displayed.</summary>
/// <remarks>This value only has effect if Persist is true.</remarks>
public bool SaveDisplayed
{
get
{
return _saveDisplayed;
}
set
{
_saveDisplayed = value;
}
}
private string _target = String.Empty;
/// <summary>Gets or sets the name of the target for the credentials, typically a server name.</summary>
public string Target
{
get
{
return _target;
}
set
{
if (value == null)
{
throw new ArgumentException("The target cannot be a null value.", "Target");
}
else if (value.Length > CREDUI.MAX_GENERIC_TARGET_LENGTH)
{
string message = String.Format(
Thread.CurrentThread.CurrentUICulture,
"The target has a maximum length of {0} characters.",
CREDUI.MAX_GENERIC_TARGET_LENGTH);
throw new ArgumentException(message, "Target");
}
_target = value;
}
}
private string _caption = String.Empty;
/// <summary>Gets or sets the caption of the dialog.</summary>
/// <remarks>A null value will cause a system default caption to be used.</remarks>
public string Caption
{
get
{
return _caption;
}
set
{
if (value != null)
{
if (value.Length > CREDUI.MAX_CAPTION_LENGTH)
{
string message = String.Format(
Thread.CurrentThread.CurrentUICulture,
"The caption has a maximum length of {0} characters.",
CREDUI.MAX_CAPTION_LENGTH);
throw new ArgumentException(message, "Caption");
}
}
_caption = value;
}
}
private string _message = String.Empty;
/// <summary>Gets or sets the message of the dialog.</summary>
/// <remarks>A null value will cause a system default message to be used.</remarks>
public string Message
{
get
{
return _message;
}
set
{
if (value != null)
{
if (value.Length > CREDUI.MAX_MESSAGE_LENGTH)
{
string message = String.Format(
Thread.CurrentThread.CurrentUICulture,
"The message has a maximum length of {0} characters.",
CREDUI.MAX_MESSAGE_LENGTH);
throw new ArgumentException(message, "Message");
}
}
_message = value;
}
}
private Image _banner = null;
/// <summary>Gets or sets the image to display on the dialog.</summary>
/// <remarks>A null value will cause a system default image to be used.</remarks>
public Image Banner
{
get
{
return _banner;
}
set
{
if (value != null)
{
if (value.Width != ValidBannerWidth)
{
throw new ArgumentException("The banner image width must be 320 pixels.", "Banner");
}
if (value.Height != ValidBannerHeight)
{
throw new ArgumentException("The banner image height must be 60 pixels.", "Banner");
}
}
_banner = value;
}
}
/// <summary>Shows the credentials dialog.</summary>
/// <returns>Returns a DialogResult indicating the user action.</returns>
public DialogResult Show()
{
return Show(null, this.Name, this.Password, this.SaveChecked);
}
/// <summary>Shows the credentials dialog with the specified save checkbox status.</summary>
/// <param name="saveChecked">True if the save checkbox is checked.</param>
/// <returns>Returns a DialogResult indicating the user action.</returns>
public DialogResult Show(bool saveChecked)
{
return Show(null, this.Name, this.Password, saveChecked);
}
/// <summary>Shows the credentials dialog with the specified name.</summary>
/// <param name="name">The name for the credentials.</param>
/// <returns>Returns a DialogResult indicating the user action.</returns>
public DialogResult Show(string name)
{
return Show(null, name, this.Password, this.SaveChecked);
}
/// <summary>Shows the credentials dialog with the specified name and password.</summary>
/// <param name="name">The name for the credentials.</param>
/// <param name="password">The password for the credentials.</param>
/// <returns>Returns a DialogResult indicating the user action.</returns>
public DialogResult Show(string name, string password)
{
return Show(null, name, password, this.SaveChecked);
}
/// <summary>Shows the credentials dialog with the specified name, password and save checkbox status.</summary>
/// <param name="name">The name for the credentials.</param>
/// <param name="password">The password for the credentials.</param>
/// <param name="saveChecked">True if the save checkbox is checked.</param>
/// <returns>Returns a DialogResult indicating the user action.</returns>
public DialogResult Show(string name, string password, bool saveChecked)
{
return Show(null, name, password, saveChecked);
}
/// <summary>Shows the credentials dialog with the specified owner.</summary>
/// <param name="owner">The System.Windows.Forms.IWin32Window the dialog will display in front of.</param>
/// <returns>Returns a DialogResult indicating the user action.</returns>
public DialogResult Show(IWin32Window owner)
{
return Show(owner, this.Name, this.Password, this.SaveChecked);
}
/// <summary>Shows the credentials dialog with the specified owner and save checkbox status.</summary>
/// <param name="owner">The System.Windows.Forms.IWin32Window the dialog will display in front of.</param>
/// <param name="saveChecked">True if the save checkbox is checked.</param>
/// <returns>Returns a DialogResult indicating the user action.</returns>
public DialogResult Show(IWin32Window owner, bool saveChecked)
{
return Show(owner, this.Name, this.Password, saveChecked);
}
/// <summary>Shows the credentials dialog with the specified owner, name and password.</summary>
/// <param name="owner">The System.Windows.Forms.IWin32Window the dialog will display in front of.</param>
/// <param name="name">The name for the credentials.</param>
/// <param name="password">The password for the credentials.</param>
/// <returns>Returns a DialogResult indicating the user action.</returns>
public DialogResult Show(IWin32Window owner, string name, string password)
{
return Show(owner, name, password, this.SaveChecked);
}
/// <summary>Shows the credentials dialog with the specified owner, name, password and save checkbox status.</summary>
/// <param name="owner">The System.Windows.Forms.IWin32Window the dialog will display in front of.</param>
/// <param name="name">The name for the credentials.</param>
/// <param name="password">The password for the credentials.</param>
/// <param name="saveChecked">True if the save checkbox is checked.</param>
/// <returns>Returns a DialogResult indicating the user action.</returns>
public DialogResult Show(IWin32Window owner, string name, string password, bool saveChecked)
{
if (Environment.OSVersion.Version.Major < 5)
{
throw new ApplicationException("The Credential Management API requires Windows XP / Windows Server 2003 or later.");
}
this.Name = name;
this.Password = password;
this.SaveChecked = saveChecked;
return ShowDialog(owner);
}
/// <summary>Confirmation action to be applied.</summary>
/// <param name="value">True if the credentials should be persisted.</param>
public void Confirm(bool value)
{
switch (CREDUI.ConfirmCredentials(this.Target, value))
{
case CREDUI.ReturnCodes.NO_ERROR:
break;
case CREDUI.ReturnCodes.ERROR_INVALID_PARAMETER:
// for some reason, this is encountered when credentials are overwritten
break;
default:
throw new ApplicationException("Credential confirmation failed.");
break;
}
}
/// <summary>Returns a DialogResult indicating the user action.</summary>
/// <param name="owner">The System.Windows.Forms.IWin32Window the dialog will display in front of.</param>
/// <remarks>
/// Sets the name, password and SaveChecked accessors to the state of the dialog as it was dismissed by the user.
/// </remarks>
private DialogResult ShowDialog(IWin32Window owner)
{
// set the api call parameters
StringBuilder name = new StringBuilder(CREDUI.MAX_USERNAME_LENGTH);
name.Append(this.Name);
StringBuilder password = new StringBuilder(CREDUI.MAX_PASSWORD_LENGTH);
password.Append(this.Password);
int saveChecked = Convert.ToInt32(this.SaveChecked);
CREDUI.INFO info = GetInfo(owner);
CREDUI.FLAGS flags = GetFlags();
// make the api call
CREDUI.ReturnCodes code = CREDUI.PromptForCredentials(
ref info,
this.Target,
IntPtr.Zero, 0,
name, CREDUI.MAX_USERNAME_LENGTH,
password, CREDUI.MAX_PASSWORD_LENGTH,
ref saveChecked,
flags
);
// clean up resources
if (this.Banner != null) GDI32.DeleteObject(info.hbmBanner);
// set the accessors from the api call parameters
this.Name = name.ToString();
this.Password = password.ToString();
this.SaveChecked = Convert.ToBoolean(saveChecked);
return GetDialogResult(code);
}
/// <summary>Returns the info structure for dialog display settings.</summary>
/// <param name="owner">The System.Windows.Forms.IWin32Window the dialog will display in front of.</param>
private CREDUI.INFO GetInfo(IWin32Window owner)
{
CREDUI.INFO info = new CREDUI.INFO();
if (owner != null) info.hwndParent = owner.Handle;
info.pszCaptionText = this.Caption;
info.pszMessageText = this.Message;
if (this.Banner != null)
{
info.hbmBanner = new Bitmap(this.Banner, ValidBannerWidth, ValidBannerHeight).GetHbitmap();
}
info.cbSize = Marshal.SizeOf(info);
return info;
}
/// <summary>Returns the flags for dialog display options.</summary>
private CREDUI.FLAGS GetFlags()
{
CREDUI.FLAGS flags = CREDUI.FLAGS.GENERIC_CREDENTIALS;
// grrrr... can't seem to get this to work...
// if (incorrectPassword) flags = flags | CredUI.CREDUI_FLAGS.INCORRECT_PASSWORD;
if (this.AlwaysDisplay) flags = flags | CREDUI.FLAGS.ALWAYS_SHOW_UI;
if (this.ExcludeCertificates) flags = flags | CREDUI.FLAGS.EXCLUDE_CERTIFICATES;
if (this.Persist)
{
flags = flags | CREDUI.FLAGS.EXPECT_CONFIRMATION;
if (this.SaveDisplayed) flags = flags | CREDUI.FLAGS.SHOW_SAVE_CHECK_BOX;
}
else
{
flags = flags | CREDUI.FLAGS.DO_NOT_PERSIST;
}
if (this.KeepName) flags = flags | CREDUI.FLAGS.KEEP_USERNAME;
return flags;
}
/// <summary>Returns a DialogResult from the specified code.</summary>
/// <param name="code">The credential return code.</param>
private DialogResult GetDialogResult(CREDUI.ReturnCodes code)
{
DialogResult result;
switch (code)
{
case CREDUI.ReturnCodes.NO_ERROR:
result = DialogResult.OK;
break;
case CREDUI.ReturnCodes.ERROR_CANCELLED:
result = DialogResult.Cancel;
break;
case CREDUI.ReturnCodes.ERROR_NO_SUCH_LOGON_SESSION:
throw new ApplicationException("No such logon session.");
break;
case CREDUI.ReturnCodes.ERROR_NOT_FOUND:
throw new ApplicationException("Not found.");
break;
case CREDUI.ReturnCodes.ERROR_INVALID_ACCOUNT_NAME:
throw new ApplicationException("Invalid account name.");
break;
case CREDUI.ReturnCodes.ERROR_INSUFFICIENT_BUFFER:
throw new ApplicationException("Insufficient buffer.");
break;
case CREDUI.ReturnCodes.ERROR_INVALID_PARAMETER:
throw new ApplicationException("Invalid parameter.");
break;
case CREDUI.ReturnCodes.ERROR_INVALID_FLAGS:
throw new ApplicationException("Invalid flags.");
break;
default:
throw new ApplicationException("Unknown credential result encountered.");
break;
}
return result;
}
}
public sealed class GDI32
{
private GDI32()
{ }
[DllImport("gdi32.dll", EntryPoint="DeleteObject")]
public static extern bool DeleteObject(IntPtr hObject);
}
public sealed class CREDUI
{
private CREDUI()
{ }
/// <summary>http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/authentication_constants.asp</summary>
public const int MAX_MESSAGE_LENGTH = 100;
public const int MAX_CAPTION_LENGTH = 100;
public const int MAX_GENERIC_TARGET_LENGTH = 100;
public const int MAX_DOMAIN_TARGET_LENGTH = 100;
public const int MAX_USERNAME_LENGTH = 100;
public const int MAX_PASSWORD_LENGTH = 100;
/// <summary>
/// http://www.pinvoke.net/default.aspx/Enums.CREDUI_FLAGS
/// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/dpapiusercredentials.asp
/// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/creduipromptforcredentials.asp
/// </summary>
[Flags] public enum FLAGS
{
INCORRECT_PASSWORD = 0x1,
DO_NOT_PERSIST = 0x2,
REQUEST_ADMINISTRATOR = 0x4,
EXCLUDE_CERTIFICATES = 0x8,
REQUIRE_CERTIFICATE = 0x10,
SHOW_SAVE_CHECK_BOX = 0x40,
ALWAYS_SHOW_UI = 0x80,
REQUIRE_SMARTCARD = 0x100,
PASSWORD_ONLY_OK = 0x200,
VALIDATE_USERNAME = 0x400,
COMPLETE_USERNAME = 0x800,
PERSIST = 0x1000,
SERVER_CREDENTIAL = 0x4000,
EXPECT_CONFIRMATION = 0x20000,
GENERIC_CREDENTIALS = 0x40000,
USERNAME_TARGET_CREDENTIALS = 0x80000,
KEEP_USERNAME = 0x100000,
}
/// <summary>http://www.pinvoke.net/default.aspx/Enums.CredUIReturnCodes</summary>
public enum ReturnCodes
{
NO_ERROR = 0,
ERROR_INVALID_PARAMETER = 87,
ERROR_INSUFFICIENT_BUFFER = 122,
ERROR_INVALID_FLAGS = 1004,
ERROR_NOT_FOUND = 1168,
ERROR_CANCELLED = 1223,
ERROR_NO_SUCH_LOGON_SESSION = 1312,
ERROR_INVALID_ACCOUNT_NAME = 1315
}
/// <summary>
/// http://www.pinvoke.net/default.aspx/Structures.CREDUI_INFO
/// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/credui_info.asp
/// </summary>
public struct INFO
{
public int cbSize;
public IntPtr hwndParent;
[MarshalAs(UnmanagedType.LPWStr)] public string pszMessageText;
[MarshalAs(UnmanagedType.LPWStr)] public string pszCaptionText;
public IntPtr hbmBanner;
}
/// <summary>
/// http://www.pinvoke.net/default.aspx/credui.CredUIPromptForCredentialsW
/// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/creduipromptforcredentials.asp
/// </summary>
[DllImport("credui", EntryPoint="CredUIPromptForCredentialsW", CharSet=CharSet.Unicode)]
public static extern ReturnCodes PromptForCredentials(
ref INFO creditUR,
string targetName,
IntPtr reserved1,
int iError,
StringBuilder userName,
int maxUserName,
StringBuilder password,
int maxPassword,
ref int iSave,
FLAGS flags
);
/// <summary>
/// http://www.pinvoke.net/default.aspx/credui.CredUIConfirmCredentials
/// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/creduiconfirmcredentials.asp
/// </summary>
[DllImport("credui.dll", EntryPoint="CredUIConfirmCredentialsW", CharSet=CharSet.Unicode)]
public static extern ReturnCodes ConfirmCredentials(
string targetName,
bool confirm
);
}
}
On the next page we've got some examples of how to utilise the CredentialsDialog class.
Related articles
Related discussion
-
How to optimize mysql subquery performance?
by Jayaram P (0 replies)
-
C# video Editing/rendering
by pkuchaliya (0 replies)
-
How to Fill DataSet with more records (around 1 lakh) in a faster way
by Jayaram P (0 replies)
-
Can't print on the network with MSADESS ??
by anatha1 (2 replies)
-
Very Urgent regarding deleting the images from a folder
by Nanosteps (6 replies)
Related podcasts
-
Object-Oriented Programming in Ruby
In this episode, I talk with Scott Bellware about object-oriented programming in Ruby, and Ruby's object model. This is taken from a private conversation, and the audio quality suffers at times. Much thanks to Scott for allowing this to be released.This episode of the Alt.NET Podcast is bro...
Not sure if Jim will see this due to the time lapse, but I have also see the same issue. I believe I've narrowed it down to a race condition that occurs between the time the tcpListener.EndAcceptTcpClient completed (the first time, and probably due to instantiation of the associated objects, and when the method LogonUser exits. That is, the first time, apparently the LogonUser exits before EndAcceptTcpClient has time to assigne the WindowsIdentity to Id. A quick solution is to put a boolean set a boolean flag to false at the beginning of the LogonUser that is set after the Id is set and loop at the end of the method until the flag is set trur. However, this not a good solution. I think the correct solution is to use a WaitEvent, but I havn't quick got that to work.
Alan:
First of all thank you for posting this code.
The credetials get stored in Stored User Names and Passwords if flag CREDUIFLAGSGENERICCREDENTIALS (or CREDUI.FLAGS.GENERICCREDENTIALS) is not set. It seems that flags CREDUIFLAGSGENERICCREDENTIALS and CREDUIFLAGS_PERSIST are mutually exclusive.
I modified the implementation of the GetFlags method as below (it is not fully tested). Now if you set the Persist property to true the credentials are saved against "Secure Application" server (which is somewhat odd).
private CREDUI.FLAGS GetFlags()
{
CREDUI.FLAGS flags = 0;
if( !this.Persist ) flags = flags | CREDUI.FLAGS.GENERICCREDENTIALS;
if (this.AlwaysDisplay && !this.Persist)
flags = flags | CREDUI.FLAGS.ALWAYSSHOWUI | CREDUI.FLAGS.GENERICCREDENTIALS;
if (this.ExcludeCertificates) flags = flags | CREDUI.FLAGS.EXCLUDECERTIFICATES;
if (this.Persist)
{
flags = flags | CREDUI.FLAGS.EXPECTCONFIRMATION | CREDUI.FLAGS.PERSIST;
}
else
{
flags = flags | CREDUI.FLAGS.DONOTPERSIST;
}
if (this.SaveDisplayed && !this.Persist)
flags = flags | CREDUI.FLAGS.SHOWSAVECHECKBOX | CREDUI.FLAGS.DONOT_PERSIST;
if (this.KeepName) flags = flags | CREDUI.FLAGS.KEEP_USERNAME;
return flags;
}
The 2 other changes:
1. Flag CREDUIFLAGSALWAYSSHOWUI can be set only if CREDUIFLAGSGENERICCREDENTIALS is also specified.
2. Flag CREDUI.FLAGS.SHOWSAVECHECKBOX can be set only if CREDUI.FLAGS.DONOTPERSIST is also set.
Hi, I've noticed the first time I run this it seems to return without error on the Confirm Credentials part, but Credentials are not appearing in the User Credential Manager and I can't actually access the resource that I should (I'm trying to access the running services on a w2k machine in a workgroup from my win xp machine in a domain. If I add the credentials manually via control panel-->users I can access the services without problem. Also if I try the dialog process again I get the ERRORINVALIDPARAMETER error on the Confirm Part. Just wondering if you have encountered this yourself and if you have a work around.
Cheers
Jim
This thread is for discussions of Using the Credential Management API.