Search code examples
c#asp.netasp.net-mvcpowershellpowercli

how to set a timeout when executing Powershell & Powercli commands inside asp.net mvc


I am working on an asp.net mvc web application, and i have the following code which define a loop over a list of servers and execute PowerCli commands inside my asp.net mvc for each server:-

//Start Loop
    var shell = PowerShell.Create();
    var shell2 = PowerShell.Create();
    var shell3 = PowerShell.Create();

    string PsCmd = "add-pssnapin VMware.VimAutomation.Core; $vCenterServer = '" + vCenterName + "';$vCenterAdmin = '" + vCenterUsername + "' ;$vCenterPassword = '" + vCenterPassword + "';" + System.Environment.NewLine;

    PsCmd = PsCmd + "$VIServer = Connect-VIServer -Server $vCenterServer -User $vCenterAdmin -Password $vCenterPassword;" + System.Environment.NewLine;

    PsCmd = PsCmd + "Get-VMHost " + System.Environment.NewLine;

    string PsCmd2 = "add-pssnapin VMware.VimAutomation.Core; $vCenterServer = '" + vCenterName + "';$vCenterAdmin = '" + vCenterUsername + "' ;$vCenterPassword = '" + vCenterPassword + "';" + System.Environment.NewLine;

    PsCmd2 = PsCmd2 + "$VIServer = Connect-VIServer -Server $vCenterServer -User $vCenterAdmin -Password $vCenterPassword;" + System.Environment.NewLine;

    PsCmd2 = PsCmd2 + " Get-VMHost " + vCenterName + "| Get-VMHostNetworkAdapter -VMKernel" + System.Environment.NewLine;

    shell.Commands.AddScript(PsCmd);
    shell2.Commands.AddScript(PsCmd2);

    dynamic results = shell.Invoke(); 
    dynamic results2 = shell2.Invoke();
// end of loop

but i have noted that sometimes the shell commands will hang and the execution never ends,, so can i define a timeout behavior ,, so that after 5 minutes to skip the commands if no results were returned ...


Solution

  • You will have to roll your own timeout command. Below is code I wrote based on a MSDN Blog entry by Keith Babinec - Executing PowerShell scripts from C#. I wrote the sample in Console Application for demonstration purposes only. I find it easier to see what is happen. You can convert it to Asp.Net application by removing the Console Output and other adjustments.

    Here is Program.cs

    using System;
    using System.Management.Automation;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                string script = "Write-Host \"Testing lopping...\"" + Environment.NewLine
                                + "for ($i=1; $i -le 5; $i++)" + Environment.NewLine
                                + "{" + Environment.NewLine
                                + "Write-Output $i" + Environment.NewLine
                                + "Start-Sleep -s 3" + Environment.NewLine
                                + "}" + Environment.NewLine
                                + "Write-Host \"Done!\"" + Environment.NewLine;
    
                PowerShell shell = PowerShell.Create();
                shell.AddScript(script);
    
                PowerShellHelper helper = new PowerShellHelper(shell);
                try
                {
                    // the script above should take 15 seconds to execute
    
                    // do timeout of 10 minutes
                    helper.ExecuteAsynchronously(new TimeSpan(0, 10, 0));
    
                    // do a really short timeout - 2 seconds
                    helper.ExecuteAsynchronously(new TimeSpan(0, 0, 2));
                }
                catch(TimeoutException te)
                {
                    Console.WriteLine("\n\nScript took long!");
                }
    
                Console.WriteLine("Demo Finish");
                Console.ReadLine();
            }
        }
    }
    

    And here is PowerShellHelper.cs

    using System;
    using System.Management.Automation;
    using System.Threading;
    
    // This code was build from MSDN Blogs entry by Keith Babinec
    // http://blogs.msdn.com/b/kebab/archive/2014/04/28/executing-powershell-scripts-from-c.aspx
    
    namespace ConsoleApplication1
    {
        class PowerShellHelper
        {
            private PowerShell shell_;
    
            public PowerShellHelper(PowerShell shell)
            {
                shell_ = shell;
            }
            public void ExecuteAsynchronously(TimeSpan timeout)
            {
                // prepare a new collection to store output stream objects
                PSDataCollection<PSObject> outputCollection = new PSDataCollection<PSObject>();
                outputCollection.DataAdded += outputCollection_DataAdded;
    
                // begin invoke execution on the pipeline
                // use this overload to specify an output stream buffer
                IAsyncResult result = shell_.BeginInvoke<PSObject, PSObject>(null, outputCollection);
    
                // start the timer
                DateTime startTime = DateTime.Now;
    
                // do something else until execution has completed.
                // this could be sleep/wait, or perhaps some other work
                while (result.IsCompleted == false)
                {
                    Console.WriteLine("Waiting for pipeline to finish...");
                    Thread.Sleep(100);
    
                    // we check on our timeout here
                    TimeSpan elasped = DateTime.Now.Subtract(startTime);
                    if (elasped > timeout)
                    {
                        // we can do a few things here, I like to throw exception
                        throw new TimeoutException("Powershell script taking too long");
                    }
                }
    
                Console.WriteLine("Execution has stopped. The pipeline state: " + shell_.InvocationStateInfo.State);
    
                foreach (PSObject outputItem in outputCollection)
                {
                    //TODO: handle/process the output items if required
                    if (outputItem != null)
                    {
                        Console.WriteLine(outputItem.BaseObject.ToString());
                    }
                }
            }
    
            /// <summary>
            /// Event handler for when data is added to the output stream.
            /// </summary>
            /// <param name="sender">Contains the complete PSDataCollection of all output items.</param>
            /// <param name="e">Contains the index ID of the added collection item and the ID of the PowerShell instance this event belongs to.</param>
            private void outputCollection_DataAdded(object sender, DataAddedEventArgs e)
            {
                // do something when an object is written to the output stream
                Console.WriteLine("Object added to output.");
            }
        }
    }