Search code examples
c#authenticationpasswordsxensecurestring

How do I login to a XEN session from a C# program using a secure string password?


I'm using PowerShell 5.1, Visual Studio 2017, C# , and XenServer SDK 7.1.1.

Using Get-Credentials and Export-CliXml in a PowerShell program, I've saved my pool master server login credentials for the root user to an XML credentials file (xml_creds.xml)

Now, I want to create and login to a session using C# (see code below). As you can see, I'm forced to convert my secure string to a plain text string to satisfy the signature for the Xen .NET API's login_with_passwordmethod.

Using the API, how do I login to the session using a secure string?

Code

try
{

    securedPassword = new SecureString();
    string unsecuredPassword = "";

    Runspace rs = RunspaceFactory.CreateRunspace();
    rs.Open();

    Pipeline pipeline = rs.CreatePipeline(@"Import-CliXml 'C:\foo\xml_creds.xml';");

    Collection<PSObject> results = pipeline.Invoke();

    if (results.Count == 1)
    {
        PSObject psOutput = results[0];

        securedPassword = ((PSCredential)psOutput.BaseObject).Password;
        unsecuredPassword = new System.Net.NetworkCredential(string.Empty, securedPassword).Password;
        username = ((PSCredential)psOutput.BaseObject).UserName;

        rs.Close();

        session = new Session(hostname, port);

        session.login_with_password(username, unsecuredPassword, API_Version.API_1_3);
    }
    else
    {
        throw new System.Exception("Could not obtain pool master server credentials");
    }
}
catch (Exception e1)
{
    System.Console.WriteLine(e1.Message);
}
finally
{
    if (securedPassword != null)
    {
        securedPassword.Dispose();
    }

    if (session != null)
    {
        session.logout(session);
    }
}

Solution

  • I contacted Citrix.

    The Xen API does not provide a mechanism for logging into a session using a secure string password.

    So, I ended up using a C# program that executes two PowerShell scripts that do support a secure string password.

    See the code below.

    Notes:

    I have the 7.1.1 XenServerPSModule installed in %USERPROFILE%\Documents\WindowsPowerShell\Modules\XenServerPSModule. This module provides the Connect-XenServer cmdlet

    I created the xml credentials file using PowerShell get-credentials followed by using export-clixml

    I loaded the requisite System.Management.Automation reference by installing Microsoft.PowerShell.5.ReferenceAssemblies from NuGet

    form1.cs (form has just a button)

    using System;
    using System.Windows.Forms;
    using XenSnapshotsXenAccess;
    
    namespace Create_XenSnapshotsUi
    {
        public partial class Form1 : Form
        {
            XenSessionAccess xenSession = null;
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void Button1_Click(object sender, EventArgs e)
            {
    
                xenSession = new XenSessionAccess("https://xxx.xx.x.x", @"C:\foo\xml_credentials.xml");
    
                xenSession.Logout();
    
            }
        }
    }
    

    XenSessionAccess class

    using System;
    using System.Collections.ObjectModel;
    using System.Management.Automation;
    using System.Management.Automation.Runspaces;
    using XenAPI;
    
    namespace XenSnapshotsXenAccess
    {
        public class XenSessionAccess
        {
            private Session xenSession = null;
    
            public Session XenSession { get => xenSession; set => xenSession = value; }
    
            public void Logout()
            {
                if (XenSession != null)
                {
                    XenSession.logout(XenSession);
                }
            }
    
            public XenSessionAccess(string poolMasterServerUrl, string xml_creds_path)
            {
                Collection<PSObject> results = null;
                PSCredential psCredential = null;
    
                //https://learn.microsoft.com/en-us/powershell/developer/hosting/creating-an-initialsessionstate
    
                //Createdefault2* loads only the commands required to host Windows PowerShell (the commands from the Microsoft.PowerShell.Core module.
                InitialSessionState initialSessionState = InitialSessionState.CreateDefault2();
    
                using (Runspace runSpace = RunspaceFactory.CreateRunspace(initialSessionState))
                {
                    runSpace.Open();
    
                    using (PowerShell powerShell = PowerShell.Create())
                    {
                        powerShell.Runspace = runSpace;
                        powerShell.AddCommand("Import-CliXml");
    
                        powerShell.AddArgument(xml_creds_path);
                        results = powerShell.Invoke();
    
                        if (results.Count == 1)
                        {
                            PSObject psOutput = results[0];
                            //cast the result to a PSCredential object
                            psCredential = (PSCredential)psOutput.BaseObject;
                        }
                        else
                        {
                            throw new System.Exception("Could not obtain pool master server credentials");
                        }
                    }
    
                    runSpace.Close();
                }
    
                initialSessionState = InitialSessionState.CreateDefault2();
                initialSessionState.ImportPSModule(new string[] { "XenServerPSModule" });
                initialSessionState.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.Unrestricted;
    
                SessionStateVariableEntry psCredential_var = new SessionStateVariableEntry("psCredential", psCredential, "Credentials to log into pool master server");
                initialSessionState.Variables.Add(psCredential_var);
    
                SessionStateVariableEntry poolUrl_var = new SessionStateVariableEntry("poolUrl", poolMasterServerUrl, "Url of pool master server");
                initialSessionState.Variables.Add(poolUrl_var);
    
                using (Runspace runSpace = RunspaceFactory.CreateRunspace(initialSessionState))
                {
                    runSpace.Open();
    
                    using (PowerShell powerShell = PowerShell.Create())
                    {
                        powerShell.Runspace = runSpace;
                        powerShell.AddScript(@"$psCredential | Connect-XenServer -url $poolUrl -SetDefaultSession -PassThru");
                        results = powerShell.Invoke();
                    }
    
                    if (results.Count == 1)
                    {
                        PSObject psOutput = results[0];
                        //cast the result to a XenAPI.Session object
                        XenSession = (Session)psOutput.BaseObject;
                    }
                    else
                    {
                        throw new System.Exception(String.Format("Could not create session for {0}", poolMasterServerUrl));
                    }
    
                    runSpace.Close();
                }
            }
        }
    }