Avatar of David L. Hansen
David L. Hansen
Flag for United States of America asked on

How to get a Powershell script to launch from Visual Studio

I have a PS script (myscript.ps1) that runs fine when launched from the PS command line. However, when I launch it from a Visual Studio app using the following code, the PS blue screen flashes quickly but nothing else happens (the expectation is the creation of a file). There doesn't appear to be any errors but I suspect one is listed in PS's blue screen.

Btw, launching PS scripts from VS is new to me, although I have set PS's rights and it's trusted folders already.

Process.Start("powershell", "-noexit -file 'c:\Temp\myScript.ps1'")

Open in new window

Powershell.NET ProgrammingVisual Basic.NET

Avatar of undefined
Last Comment
David L. Hansen

8/22/2022 - Mon
Chris Dent

Depending on your goals there's another way to go about this.

A mini-class with a PS wrapper :)
Add-Type -TypeDefinition @"
using System.Management.Automation;
using System.Management.Automation.Runspaces;

public class PS
{
    PowerShell host = PowerShell.Create(InitialSessionState.CreateDefault());

    public object RunScript(string path)
    {
        return host.AddScript(path).Invoke();
    }
}
"@ -ReferencedAssemblies ([PowerShell].Assembly.Location)

Open in new window

Once it's created, the method can be invoked after you instantiate the class.
[PS]::new().RunScript('C:\Development\scratch\test.ps1')

Open in new window

The advantage of this approach is that you can get detailed error information. Runtime errors (terminating errors) will throw as exceptions when Invoke is called. Non-terminating errors are written to host.Streams.Error.
Chris Dent

I just noticed the VB .NET tag. Is the C# example easy enough for you to translate? I can change it, but I always have to double check VB .NET syntax which is only worthwhile if you find it to be.
David L. Hansen

ASKER
No Chris, C# is fine. I'll give it a shot and see how it goes.

ps. I have a simple text log file created which I'd like to be filled with any errors. Can I pass the errors listed in the host.Streams.Error object to that log file easily (If it's not too much trouble...I can open another question for that if you need)?
All of life is about relationships, and EE has made a viirtual community a real community. It lifts everyone's boat
William Peck
David Johnson, CD

Process.Start("powershell", "-noexit  -ExecutionPolicy Bypass -file 'c:\Temp\myScript.ps1'")

Open in new window

Chris Dent

Yes, you can. The Error collection contains a list of ErrorRecord objects. This example should push that to the console, it shows how I would distinguish between the different types of error (runtime vs managed).
Add-Type -TypeDefinition @"
Imports System
Imports System.Management.Automation
Imports System.Management.Automation.Runspaces

Public Class PS
    Dim host As PowerShell = PowerShell.Create(InitialSessionState.CreateDefault())

    Public Function RunScript(path As String) As object
        Try
            Dim value As Object = host.AddScript(path).Invoke()
            If host.HadErrors Then
                For Each errorRecord as ErrorRecord in host.Streams.Error
                    Console.WriteLine(errorRecord.Exception.Message)
                Next
            Else
                Return value
            End If
        Catch e As Exception
            Console.WriteLine(e.Message)
        End Try

        Return Nothing
    End Function
End Class
"@ -ReferencedAssemblies ([PowerShell].Assembly.Location) -Language VisualBasic

Open in new window

David L. Hansen

ASKER
David,

Your "Process.Start" approach results in the same blue screen flash as before and no file creation. I'll keep watching for more suggestions from you while I test Chris's code.
⚡ FREE TRIAL OFFER
Try out a week of full access for free.
Find out why thousands trust the EE community with their toughest problems.
David L. Hansen

ASKER
Chris,

The "Host" object that we're instantiating has no "Create" method attached to it (at least not in the namespace I'm seeing). It does however, have a CreateNestedPowerShell method instead. If I use that one instead, I do find the "AddScript" method but  no "HadErrors" property.

I'm using/importing all three namespaces you have in your example. I'm also running VS2015.
Thoughts?
Chris Dent

It's a static method on the System.Management.Automation.PowerShell class. Be a touch careful about assembly versions though, Visual Studio is terrible at picking a modern version when selecting references. I normally feed it the path revealed by the snippet below when adding the reference:
[PowerShell].Assembly.Location

Open in new window

That gets it the assembly associated with PowerShell 5.1 not PowerShell 2.0.
David L. Hansen

ASKER
Working on that now....
Experts Exchange is like having an extremely knowledgeable team sitting and waiting for your call. Couldn't do my job half as well as I do without it!
James Murphy
Fernando Soto

Have a look at this web page. Calling a PowerShell Script From Your .NET Code.
David L. Hansen

ASKER
Chris,

Like you say, I think if we can persuade VS to take a different PowerShell version, we'd be home free.
This code however:
"Add-Type -TypeDefinition @" and "@ -ReferencedAssemblies ([PowerShell].Assembly.Location) -Language VisualBasic"

Open in new window

surrounding the class code causes the entire class code to display as quoted-text and red-marks the whole thing with a "Statement cannot appear outside of a method body" error.

I'm putting this in an existing VB project so the VB format would be preferable, but whether in C# or VB, can you show me a working example of the ReferencedAssemblies approach please? I don't mind rewriting it to fit my environment/local-paths, etc.

Thanks in advance!
ASKER CERTIFIED SOLUTION
Chris Dent

THIS SOLUTION ONLY AVAILABLE TO MEMBERS.
View this solution by signing up for a free trial.
Members can start a 7-Day free trial and enjoy unlimited access to the platform.
See Pricing Options
Start Free Trial
GET A PERSONALIZED SOLUTION
Ask your own question & get feedback from real experts
Find out why thousands trust the EE community with their toughest problems.
David L. Hansen

ASKER
This is the resulting path given by PS:

"C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\System.Management
.Automation.dll"

Yesterday actually, I included that particular reference in the project, which was when I got the strange "CreateNestedPowerShell" method instead of the simpler "Create". Luckily, it does appear that I can download any version of the various PS namespaces through NuGet though, but I'm not sure which I need.

Here are the options:
https://www.nuget.org/profiles/PowerShellTeam
⚡ FREE TRIAL OFFER
Try out a week of full access for free.
Find out why thousands trust the EE community with their toughest problems.
Chris Dent

Hmm okay, well at least that bit is right.

The CreateNestedPowerShell method is a method on the instantiated host, we don't need that one. To create the host in the first place you want the static method on the PowerShell type.

Where this:
PowerShell.Create(InitialSessionState.CreateDefault())

Open in new window

Is this static method:

https://msdn.microsoft.com/en-us/library/hh470726(v=vs.85).aspx?cs-save-lang=1&cs-lang=vb#code-snippet-1
David L. Hansen

ASKER
Using the url you just posted, I tracked backwards (through the webpage's bread-crumb path) and found no specific version number referenced. Perhaps you can look in your object explorer in VS and see the correct version number that way?
Chris Dent

This is my assemblies full name:

System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35

I think you have the right assembly.

Going back to this:

> The "Host" object that we're instantiating has no "Create" method attached to it

The create method is not on the instantiated "Host" object. It's a static method on the PowerShell type (does not require an instantiated object).

You're right that the instantiated "Host" object does not have such a method. But if you've instantiated it, you're done with that part and it's on to loading a script into the Commands collection (using the AddScript method).
This is the best money I have ever spent. I cannot not tell you how many times these folks have saved my bacon. I learn so much from the contributors.
rwheeler23
David L. Hansen

ASKER
It worked! :)

Yesterday, for whatever reason (operator error I'm sure) this line of code:
Dim host As PowerShell = PowerShell.Create(InitialSessionState.CreateDefault())

Open in new window

wasn't liked by VS so I changed it to this:
Dim host As PowerShell

Open in new window

and followed that up with the "Create" static method used in a constructor (Sub New) which it didn't like either (saying that a "declaration is expected").

At any rate, it works!

Thank you, thank you!
Chris Dent

Yay :) Good work :)
David L. Hansen

ASKER
A big thank you to everyone who helped!
Hat-tip to David J. and Fernando.
⚡ FREE TRIAL OFFER
Try out a week of full access for free.
Find out why thousands trust the EE community with their toughest problems.
David L. Hansen

ASKER
For any future readers of this thread, I later on needed to get this working across a network which required my changing some things. The scope and ExecutionPolicy needed to be altered and since catching all errors was still important to me (which can't be done through a simple shell process) I posted this additional question which was very useful.