Custom PSHost, Get-Credential cmdlet in C#

634 Views Asked by At

i am trying to implement a custom PSHost class for the purpose of crafting GUIs for Power Shell scripts. I have taken the code here as a base and started adapting it for GUI Projects https://msdn.microsoft.com/en-us/library/ee706557%28v=vs.85%29.aspx

All was fine until i tried to run the Get-Credential cmdlet and got a error that the method is not implemented(ooouuuu)... The initial code was these two methods:

public override PSCredential PromptForCredential(
                                                 string caption, 
                                                 string message, 
                                                 string userName, 
                                                 string targetName)
{
  throw new NotImplementedException(
                       "The method or operation is not implemented.");
}

public override PSCredential PromptForCredential(
                                   string caption, 
                                   string message, 
                                   string userName, 
                                   string targetName, 
                                   PSCredentialTypes allowedCredentialTypes, 
                                   PSCredentialUIOptions options)
{
  throw new NotImplementedException(
                          "The method or operation is not implemented.");
}

So after some research i implemented the dialogue like this:

    [DllImport("ole32.dll")]
    public static extern void CoTaskMemFree(IntPtr ptr);

    [DllImport("credui.dll", CharSet = CharSet.Auto)]
    private static extern int CredUIPromptForWindowsCredentials(ref CREDUI_INFO notUsedHere, int authError, ref uint authPackage, IntPtr InAuthBuffer, uint InAuthBufferSize, out IntPtr refOutAuthBuffer, out uint refOutAuthBufferSize, ref bool fSave, int flags);

    [DllImport("credui.dll", CharSet = CharSet.Auto)]
    private static extern bool CredUnPackAuthenticationBuffer(int dwFlags, IntPtr pAuthBuffer, uint cbAuthBuffer, StringBuilder pszUserName, ref int pcchMaxUserName, StringBuilder pszDomainName, ref int pcchMaxDomainame, StringBuilder pszPassword, ref int pcchMaxPassword);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    private struct CREDUI_INFO
    {
        public int cbSize;
        public IntPtr hwndParent;
        public string pszMessageText;
        public string pszCaptionText;
        public IntPtr hbmBanner;
    }

    private enum PromptForWindowsCredentialsFlags
    {
        ...
    }

    public override PSCredential PromptForCredential(
                                       string caption,
                                       string message,
                                       string userName,
                                       string targetName,
                                       PSCredentialTypes allowedCredentialTypes,
                                       PSCredentialUIOptions options)
    {
        CREDUI_INFO credui = new CREDUI_INFO();
        credui.pszCaptionText = "Please enter the credentails";
        credui.pszMessageText = "DisplayedMessage";
        credui.cbSize = Marshal.SizeOf(credui);
        uint authPackage = 0;
        IntPtr outCredBuffer = new IntPtr();
        uint outCredSize;
        bool save = false;
        int result = CredUIPromptForWindowsCredentials(ref credui, 0, ref authPackage, IntPtr.Zero, 0, out outCredBuffer, out outCredSize, ref save, 1 /* Generic */);

        var usernameBuf = new StringBuilder(100);
        var passwordBuf = new StringBuilder(100);
        var domainBuf = new StringBuilder(100);

        int maxUserName = 100;
        int maxDomain = 100;
        int maxPassword = 100;
        if (result == 0)
        {
            if (CredUnPackAuthenticationBuffer(0, outCredBuffer, outCredSize, usernameBuf, ref maxUserName, domainBuf, ref maxDomain, passwordBuf, ref maxPassword))
            {
                //clear the memory allocated by CredUIPromptForWindowsCredentials
                CoTaskMemFree(outCredBuffer);
                SecureString secureString = new SecureString();
                foreach (char c in passwordBuf.ToString())
                {
                    secureString.AppendChar(c);
                }
                return new PSCredential(usernameBuf.ToString(), secureString);
            }
        }
        return null;
    }

This works fine but there is one snag, when running the Get-Credential cmdlet it prompts for a value for the Credential parameter, regardless of any input after hitting return the dialogue pops up and everything works as expected. Doing the same in ISE the dialogue pops up directly without any prompt. I thought it was due to the arguments of the method but making them optional by defining a default value did not make a difference. It might be due to how the cmdlet is defined in the PS environment so my question is: How can i make it so that running the cmdlet will jump straight to the dialogue? Is it possible to define a default value for the Credential parameter to bind to? I'm guessing this is how ISE gets around this or am i missing something?

1

There are 1 best solutions below

0
On BEST ANSWER

I took some time and experimenting but i found it, the mandatory parameter binding is handled by the public override Dictionary<string, PSObject> Prompt(string caption, string message, Collection<FieldDescription> descriptions) method in the PSHostUserInterface interface before PromptForCredential is called.