2

I am trying to execute a PowerShell script using C#

My script requires parameters to run otherwise, it'll throw an error.

Here is a stripped down version of my script

param ($url)
if ($url -eq $null) {
    throw "No Url was provided" 
}

write-host "$url was the provided value"

Now, using C# I am trying to execute the script as follow

try {
    var defaultSessionState = InitialSessionState.CreateDefault();
    defaultSessionState.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.Unrestricted;

    using PowerShell ps = PowerShell.Create(defaultSessionState);

    ps.AddScript(@"d:\test.ps1");
    ps.AddParameter("url", "http://example.com/test/");
    ps.Runspace.SessionStateProxy.SetVariable("ErrorActionPreference", "stop");
} 
catch (Exception e) {

}

But I keep getting No Url was provided error. How can I correctly pass the parameter to my script, and how to access it?

mklement0
  • 382,024
  • 64
  • 607
  • 775
Jaylen
  • 39,043
  • 40
  • 128
  • 221
  • 2
    Change `ps.AddScript("d:\test.ps1")` to `ps.AddCommand("d:\test.ps1")` – Mathias R. Jessen May 18 '20 at 22:56
  • 1
    [Docs](https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.powershell.addscript?view=pscore-6.2.0] addScript takes in a string and executes that... considers the script to be executable in powershell (not a file) – Jawad May 19 '20 at 03:09

2 Answers2

2

AddScript() is for executing raw code - D:\test.ps1 happens to be valid code, but it is more meant for dropping in a full self-contained script as a string.

You'll want to add the script file reference as a command, at which point we can apply parameters - for this, use AddCommand() instead:

using (PowerShell ps = PowerShell.Create(defaultSessionState))
{
    ps.AddCommand("d:\test.ps1");
    ps.AddParameter("url", "http://example.com/test/");
    ps.AddParameter("ErrorAction", "Stop");
    // or
    ps.Runspace.SessionStateProxy.SetVariable("ErrorActionPreference", "stop");
}
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
2

Mathias R. Jessen's helpful answer provides an effective solution, but let me expand on the explanation:

The PowerShell SDK's .AddScript() method is somewhat poorly named in that it doesn't execute a script file, but an arbitrary piece of PowerShell code. That is, it expects a string containing entire PowerShell statements, in other words: the contents of a script file - not the script file's path.

Given that in PowerShell a piece of reusable code is represented as a script block, something like .AddScriptBlock() would be a less confusing name (though note that you must not enclose the string containing the statements to execute in { ... } when you call .AddScript()).[1]

Mathias's answer shows how .AddCommand() is the right method to use, because a command in PowerShell is anything that represents something that can be directly executed: aliases, functions, cmdlets, external executables - and also script files.


As for what you tried:

String d:\test.ps1 is interpreted as a piece of PowerShell code (the same way it would be if you submitted this string on the command line). That code happens to call a script file, but does so without arguments.
That is, your script was called, but without the parameter you added via .AddParameter().

  • Also note that if the script path happened to contain spaces or embedded variable references, you'd have to use embedded quoting and call the result via &, the call operator; e.g.,
    .AddScript(@"& 'd:\test 1.ps1'"); - see this answer to a closely related question for details.

However, note that your .AddParameter() call technically still worked, but it was the script block as a whole that received the parameter, not the (one) statement inside it - the call to d:\test.ps1.[2]

Technically, it would have been possible to relay the arguments to the script-file invocation, by way of splatting the automatic $args variable (in which all arguments are collected, in the absence of explicit parameter declarations via a param(...) block):

ps.AddScript(@"d:\test.ps1 @args");

That said, if all your code does is to invoke a single script file (with arguments), the .AddCommand() solution is both simpler and more efficient.

As an aside: .AddScript(), unlike .AddCommand(), is not subject to the executing machine's PowerShell execution policy, assuming that the statements passed don't try to invoke script files; that is, statements composed only of expressions and commands other than script files (*.ps1) execute irrespective of what execution policy is in effect, though note that some modules automatically try to execute script files on import.


[1] .AddScript("..."), expressed in PowerShell code, effectively does the following:
. { ... }
That is, it parses the string as a script block, and executes it via ., the dot-sourcing operator (when .Invoke() is later called).

[2] As stated, what you pass to .AddScript() is parsed into a script block, and script blocks - like functions and script files - can accept arguments, both positionally via the automatic $args variable and - with explicitly declared parameters via a param(...) block - named. To use a simple example: .AddScript('Write-Output $args[0]').AddArgument('foo') is the equivalent of . { Write-Output $args[0] } 'foo'

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Great answer. Passing parameters to a script, is it a feature that we can rely on? Or does it just happen to work? Can you point to the documentation for this feature? I really want to use it, but I am afraid it might break in the future. – Monticola Explorator Oct 22 '21 at 06:34
  • 1
    Thanks, @MonticolaExplorator. Yes, you can rely on this feature. Please see the second footnote I've just added to the answer. – mklement0 Oct 22 '21 at 15:15