Calling a PowerShell Script From Your .NET Code

Tips Search

Calling a PowerShell Script From Your .NET Code

  • Comments 18

 

Well, Mike F. reminded me to write this article on 5/29 and I am FINALLY getting around to doing it.  Also wanted to give a shout out to Max T. who provided some inspiration on this one as well.  For the code, I borrow very heavily from the article written by Jean-Paul Mikkers found at: 

http://www.codeproject.com/KB/cs/HowToRunPowerShell.aspx

 

NOTE:  There is an async version of his code that he does as a follow up.  I don't use it because I want to remove extra code noise to focus on the act of calling the script itself.  If you want to take a stab at the async version (and there are LOTS of good reasons to do so) then you can go here:

http://www.codeproject.com/KB/threads/AsyncPowerShell.aspx 

 

So, on to the goodness!

 

First, you need to make sure you have Visual Studio (any version) installed and have PowerShell installed.  You can get PowerShell from here:

http://www.microsoft.com/windowsserver2003/technologies/management/powershell/default.mspx

 

You will also need to do this (WARNING:  MAJOR SECURITY ISSUE HERE AND THIS IS JUST FOR TESTING SO DON'T DO THIS ON PRODUCTION MACHINES):

http://www.cubiczone.com/Articles/tabid/65/EntryID/17/Default.aspx

 

 

 

1.   Now, let's crank out a simple PoweShell script that we are interested in calling.  We will call a simple script that takes a couple of numbers and returns the sum of those numbers.  This may seem overly simplistic but it is an easy way to demonstrate a complete round-trip between our app and the script without getting bogged down in extra BS that comes with a fancier script.  I'll call the script AddItUp.ps1 and it is included in the source code download, just put it anywhere you can get to easily.  Feel free to dig into the guts of it later on but for now just assume it does what we need it to do. 

 

Here is the code for the script if you just want to make your own real quick:

 

# begin

function AddStuff($x,$y)
{
   $x + $y
}

 

AddStuff 6 5

# end

 

NOTE:  Some inspiration and just a cool site for scripts came from http://www.powershellpro.com just as an fyi

 

 

 

2.  Test the script by opening PowerShell and navigating to the directory where it is and typing what you see in the graphic, you should get the expected result.

image

 

 

 

 

3.  Okay!  We have a script that works but now what?  Well we have to call that puppy from our code so let's create a project and get ready to make our magic happen.  Crank out a new Windows App for us to use.  Call the Project CallMeCS or CallMeVB depending on your language.

 

 

image

 

 

 

 

4.  For the interface, just gimme a button and a label.  Resize the form a bit so we don't have wasted space.  Real simple stuff...

 

image

 

 

 

5.  Double-click on the button to go into our code.

 

C#:

image

 

VB:

image

 

 

 

6.  Now we need to add an assembly that is one of a set we got with our install of PowerShell.  You can find these assemblies at C:\Program Files\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0

 

image

 

7.  Right-click on your project and choose Add Reference...

 

image

 

 

8.  Select the Browse Tab and locate these assemblies then add a reference to the System.Management.Automation.dll

NOTE:  If you want to dig deeper into the contents of this namespace, you can check it out here:  http://msdn.microsoft.com/en-us/library/system.management.automation(VS.85).aspx

 

image

 

 

9.  Now that we have our reference we need to add some using/imports statements to make getting to the classes we want to use easier.  Make sure to put these at the top of your code page outside any other code. 

 

C#:

using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.IO;

 

VB:

Imports System.Collections.ObjectModel
Imports System.Management.Automation
Imports System.Management.Automation.Runspaces
Imports System.Text
Imports System.IO

 

 

10.  Okay, this next part is a little funkier.  While I liked the code that Mikkers had, I wanted to be able to load up a file from my file system and use it instead of just putting code into a textbox.  That created some VERY interesting new challenges but the end result worked out well.  So, to that end, we will create two helper methods:  RunScript and LoadScript.  RunScript is the code essentially unchanged from Mikkers' article and LoadScript is my helper function that will load the contents of a script file and return a string.

 

 

11.  Let's begin with the RunScript method.  We will add this method to the Form1 class to make our life easier. 

 

C#:

private string RunScript(string scriptText)
   {
       // create Powershell runspace
       Runspace runspace = RunspaceFactory.CreateRunspace();

       // open it
       runspace.Open();

       // create a pipeline and feed it the script text
       Pipeline pipeline = runspace.CreatePipeline();
       pipeline.Commands.AddScript(scriptText);

       // add an extra command to transform the script output objects into nicely formatted strings
       // remove this line to get the actual objects that the script returns. For example, the script
       // "Get-Process" returns a collection of System.Diagnostics.Process instances.
       pipeline.Commands.Add("Out-String");

       // execute the script
       Collection<PSObject> results = pipeline.Invoke();

       // close the runspace
       runspace.Close();

       // convert the script result into a single string
       StringBuilder stringBuilder = new StringBuilder();
       foreach (PSObject obj in results)
       {
           stringBuilder.AppendLine(obj.ToString());
       }

       // return the results of the script that has
       // now been converted to text
       return stringBuilder.ToString();
   }

 

VB:

' Takes script text as input and runs it, then converts
    ' the results to a string to return to the user
    Private Function RunScript(ByVal scriptText As String) As String

        ' create Powershell runspace
        Dim MyRunSpace As Runspace = RunspaceFactory.CreateRunspace()

        ' open it
        MyRunSpace.Open()

        ' create a pipeline and feed it the script text
        Dim MyPipeline As Pipeline = MyRunSpace.CreatePipeline()

        MyPipeline.Commands.AddScript(scriptText)

        ' add an extra command to transform the script output objects into nicely formatted strings
        ' remove this line to get the actual objects that the script returns. For example, the script
        ' "Get-Process" returns a collection of System.Diagnostics.Process instances.
        MyPipeline.Commands.Add("Out-String")

        ' execute the script
        Dim results As Collection(Of PSObject) = MyPipeline.Invoke()

        ' close the runspace
        MyRunSpace.Close()

        ' convert the script result into a single string
        Dim MyStringBuilder As New StringBuilder()

        For Each obj As PSObject In results
            MyStringBuilder.AppendLine(obj.ToString())
        Next

        ' return the results of the script that has
        ' now been converted to text
        Return MyStringBuilder.ToString()

    End Function

 

 

12.  Now we want to add in our LoadScript method to make getting the script into a variable easier.

 

C#:

// helper method that takes your script path, loads up the script
        // into a variable, and passes the variable to the RunScript method
        // that will then execute the contents
        private string LoadScript(string filename)
        {
            try
            {
                // Create an instance of StreamReader to read from our file.
                // The using statement also closes the StreamReader.
                using (StreamReader sr = new StreamReader(filename))
                {

                    // use a string builder to get all our lines from the file
                    StringBuilder fileContents = new StringBuilder();

                    // string to hold the current line
                    string curLine;

                    // loop through our file and read each line into our
                    // stringbuilder as we go along
                    while ((curLine = sr.ReadLine()) != null)
                    {
                        // read each line and MAKE SURE YOU ADD BACK THE
                        // LINEFEED THAT IT THE ReadLine() METHOD STRIPS OFF
                        fileContents.Append(curLine + "\n");
                    }

                    // call RunScript and pass in our file contents
                    // converted to a string
                    return fileContents.ToString();
                }
            }
            catch (Exception e)
            {
                // Let the user know what went wrong.
                string errorText = "The file could not be read:";
                errorText += e.Message + "\n";
                return errorText;
            }

        }

 

VB:

' helper method that takes your script path, loads up the script
    ' into a variable, and passes the variable to the RunScript method
    ' that will then execute the contents
    Private Function LoadScript(ByVal filename As String) As String

        Try

            ' Create an instance of StreamReader to read from our file.
            ' The using statement also closes the StreamReader.
            Dim sr As New StreamReader(filename)

            ' use a string builder to get all our lines from the file
            Dim fileContents As New StringBuilder()

            ' string to hold the current line
            Dim curLine As String = ""

            ' loop through our file and read each line into our
            ' stringbuilder as we go along
            Do
                ' read each line and MAKE SURE YOU ADD BACK THE
                ' LINEFEED THAT IT THE ReadLine() METHOD STRIPS OFF
                curLine = sr.ReadLine()
                fileContents.Append(curLine + vbCrLf)
            Loop Until curLine Is Nothing

            ' close our reader now that we are done
            sr.Close()

            ' call RunScript and pass in our file contents
            ' converted to a string
            Return fileContents.ToString()

        Catch e As Exception
            ' Let the user know what went wrong.
            Dim errorText As String = "The file could not be read:"
            errorText += e.Message + "\n"
            Return errorText
        End Try

    End Function

 

13.  Finally, we just need to add some code for our button's click event.

 

C#:

private void button1_Click(object sender, EventArgs e)
        {
            // run our script and put the result into our textbox
            // NOTE: make sure to change the path to the correct location of your script
            textBox1.Text = RunScript(LoadScript(@"c:\users\zainnab\AddItUp.ps1"));
        }

 

VB:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        'run our script and put the result into our textbox
        'NOTE: make sure to change the path to the correct location of your script
        TextBox1.Text = RunScript(LoadScript("c:\users\zainnab\AddItUp.ps1"))
End Sub

 

14.  That's it!!  You should be able to run your code and you should get this:

image

 

 

Don't sweat it if you think this is a lot to type.  I have included the source code for you to use.  Enjoy!

Attachment: CallingPowershellFromCode.zip
  • Webcasts Page MSDN and TechNet Search Pages MSDN Webcast Page: http://msdn2.microsoft.com/en-us/events/aa740361.aspx

  • Hey Now Zain,

    Nice post, Max sure loves his Powershell. Way to put it together. Also I've been in SL & attended a session on Microsoft island. As always,

    Thx 4 the info,

    Catto

  • Great content!! Its good to see different techniques from other people.  

    Thanks for the info and keep the good work,

    Max Trinidad

  • wanted to know how to handle if you want to pass in the numbers as parms?

  • haven't tested it but i would think you just create a script that accepts params then concat the params when you call the script in your code.  try it and if that doen't work i'll see if i can crank a solution.

  • zainnab,

    I also have a requirement to pass some params ... I am trying your solution of concat the params, but not sure how I pass them to the methods you have created ... Am i missing something?

    The LoadScript method only accepts filename as a param, so how would passing fileName param param be evaulated by the StreamReader in the LoadScript Method?

  • Wow, this is what I've been looking for. Thanks!

    Yes, I'm interested in how to pass params too.

  • Quick question for anybody dropping by. If I install powershell on my machine, create a script and program similar to the one above. Will it be able to run on a Windows machine without powershell? I have a tool that I develop in VB.NET and would like to use powershell scripts to assist me, but I can't gaurantee that all the machines that use my tool will have powershell installed and I don't really want to require it for running the tool.

    Thanks

  • @simps -- you have to have powershell installed anywhere you run a powershell script.  The good news is that from Win7 forward all versions of Windows come with Powershell already installed.  

    @all -- when I wrote this post a couple of years ago I think the params passing was difficult.  It may have changed since but haven't looked into it much.  Here is a SO thread that shows how to do it I think:  stackoverflow.com/.../execute-powershell-script-from-c-with-commandline-arguments

  • Did not test yet but might look something like this:

    Private Function RunScript(ByVal scriptText As String, parameters as Dictionary(Of String, String)) As String

           ' create Powershell runspace

           Dim MyRunSpace As Runspace = RunspaceFactory.CreateRunspace()

           ' open it

           MyRunSpace.Open()

           ' create a pipeline and feed it the script text

           Dim MyPipeline As Pipeline = MyRunSpace.CreatePipeline()

           Dim myCommand As Command = New Command(scriptText)

    for each p in parameters

    dim testParam as CommandParameter = new CommandParameter(p.Key,p.Value);

    myCommand.Parameters.Add(testParam);

    next

           MyPipeline.Commands.AddScript(myCommand)

           ' add an extra command to transform the script output objects into nicely formatted strings

           ' remove this line to get the actual objects that the script returns. For example, the script

           ' "Get-Process" returns a collection of System.Diagnostics.Process instances.

           MyPipeline.Commands.Add("Out-String")

           ' execute the script

           Dim results As Collection(Of PSObject) = MyPipeline.Invoke()

           ' close the runspace

           MyRunSpace.Close()

           ' convert the script result into a single string

           Dim MyStringBuilder As New StringBuilder()

           For Each obj As PSObject In results

               MyStringBuilder.AppendLine(obj.ToString())

           Next

           ' return the results of the script that has

           ' now been converted to text

           Return MyStringBuilder.ToString()

       End Function

  • Please remember the System.IO.File.ReadAllText function which would make your LoadScript function, well ... unnecessary.  Also, "return errorText;" definitely looks like the wrong way to go.  In fact, you shouldn't swallow the exception at all.  If the exception happens, the program should end, and the user should see the exception test so that they will know what went wrong.  Don't catch, just use ReadAllText.

  • Hi all,

    While  I an using this code I got worked in small commands like addition, etc. But i worked with IIS command it failed working. I give you the sample code below:

    $pathname="iis:\Sites\demoproject"

     $protocol="http"

     $port=":8080"

     $ppath="C:\inetpub\wwwroot"

     New-Item $pathname -bindings (@{protocol=$protocol;bindingInformation=$port}) -physicalPath $ppath

    Will it work with the above code?

    Felix Antony

  • Thanks! I've written quite a few programs from this.

  • Fails on this line for me:

    Dim results As Collection(Of PSObject) = MyPipeline.Invoke():

    Error 1 'Microsoft.VisualBasic.Collection' has no type parameters and so cannot have type arguments.

  • This is close for me. I really need a way to run a script and pass some required parameters to the script.

    For example. To run this script from the PS command line I navigate to the folder containing the script and type:

    PS> . .\Install-Script.ps1; Install-Script -webServer "<URL>" -uploadFile "<path to file to upload>" -force -folder "Name of Folder to create" -name "Name of upload"

    It then runs the script and enters the Install-Script function and passes all those parameters. I tries the above code in my vb project, but I don't see where I add the parameters. Also this code "MyPipeline.Commands.AddScript(myCommand)" generates an error that says "Value of type 'System.Management.Automation.Runspaces.Command cannot be converted to string'

    Any help is greatly appreciated.

Page 1 of 2 (18 items) 12
Leave a Comment
  • Please add 6 and 2 and type the answer here:
  • Post