Search code examples
c#.netpowershell.net-core.net-8.0

File cannot be loaded because running scripts is disabled on this system


I am running the Powershell script from my C# code. The same code is working for the .net 4.7.2 framework application, But when I use the same code within the same solution for the .net8 application, the same script is giving the below exception while invoking this script.

File C:\work\Powershell\SampleAppDotNet8\bin\Debug\net8.0\PS.ps1 cannot be loaded because running scripts is disabled on this system

Code:

public void RunScript(string scriptPath, Dictionary<string, object> parameters)
{
    using (var ps = PowerShell.Create())
    {
        ps.AddCommand(scriptPath);

        foreach (var param in parameters)
        {
            ps.AddParameter(param.Key, param.Value);
        }

        var results = ps.Invoke();

        foreach (var result in results)
        {
            Console.WriteLine(result);
        }

        if (ps.Streams.Error.Count > 0)
        {
            foreach (var error in ps.Streams.Error)
            {
                Console.WriteLine($"Error: {error}");
            }
        }
    }
}

I have tried checking the Execution policies as well, they are below.

PS C:\WINDOWS\system32> Get-ExecutionPolicy  -List

        Scope ExecutionPolicy
        ----- ---------------
MachinePolicy       Undefined
   UserPolicy       Undefined
      Process       Undefined
  CurrentUser          Bypass
 LocalMachine          Bypass

The only difference between the two projects is that the .net8 application refers to Microsoft.PowerShell.SDK and the .net 4.7.2 is referring the DLL from GAC (......\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\System.Management.Automation.dll)

PS Script:

param (
    [string]$name,
    [string]$username,
    [SecureString]$password
)

# Converting SecureString to plain text for display (for demonstration purposes)
if ($null -ne $securePassword) {
    $plainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
}
else {
    # Write-Error "Secure password cannot be null."
}

# Write-Host "Name: $name"
# Write-Host "Username: $username"
# Write-Host "Password: $plainPassword"


# Creating a custom object to return
$result = [PSCustomObject]@{
    Name = $name
    Username = $username
    Password = $plainPassword
}

# Returning the object
Write-Output $result

Complete Error:

C:\work\Powershell\SampleAppDotNet8\bin\Debug\net8.0>SampleAppDotNet8.exe Unhandled exception. System.Management.Automation.PSSecurityException: File C:\work\Powershell\SampleAppDotNet8\bin\Debug\net8.0\PS.ps1 cannot be loaded because running scripts is disabled on this system. For more information, see about_Execution_Policies at https://go.microsoft.com/fwlink/?LinkID=135170. ---> System.UnauthorizedAccessException: File C:\work\Powershell\SampleAppDotNet8\bin\Debug\net8.0\PS.ps1 cannot be loaded because running scripts is disabled on this system. For more information, see about_Execution_Policies at https://go.microsoft.com/fwlink/?LinkID=135170. --- End of inner exception stack trace --- at System.Management.Automation.Runspaces.PipelineBase.Invoke(IEnumerable input) at System.Management.Automation.Runspaces.Pipeline.Invoke() at System.Management.Automation.PowerShell.Worker.ConstructPipelineAndDoWork(Runspace rs, Boolean performSyncInvoke) at System.Management.Automation.PowerShell.Worker.CreateRunspaceIfNeededAndDoWork(Runspace rsToUse, Boolean isSync) at System.Management.Automation.PowerShell.CoreInvokeHelper[TInput,TOutput](PSDataCollection1 input, PSDataCollection1 output, PSInvocationSettings settings) at System.Management.Automation.PowerShell.CoreInvoke[TInput,TOutput](PSDataCollection1 input, PSDataCollection1 output, PSInvocationSettings settings) at System.Management.Automation.PowerShell.CoreInvoke[TOutput](IEnumerable input, PSDataCollection1 output, PSInvocationSettings settings) at System.Management.Automation.PowerShell.Invoke(IEnumerable input, PSInvocationSettings settings) at System.Management.Automation.PowerShell.Invoke() at PowershellAdvance.PowerShellService.RunScript(String scriptPath, Dictionary2 parameters) in C:\work\Powershell\PowershellParamsResolver\PowerShellService.cs:line 24 at PowershellAdvance.PowerShellScriptRunner.RunAsync(String jsonString, String scriptPath) in C:\work\Powershell\PowershellParamsResolver\PowerShellScriptRunner.cs:line 26 at SampleAppDotNet8.Program.Main(String[] args) in C:\work\Powershell\SampleAppDotNet8\Program.cs:line 26 at SampleAppDotNet8.Program.(String[] args)

So Do I need to handle calling the PS differently in the .net8 application?


Solution

  • The two PowerShell editions,

    • the legacy, ships-with-Windows Windows PowerShell edition (powershell.exe, whose latest and final version is 5.1.x, which is based on .NET Framework)
      vs.
    • the install-on-demand, cross-platform PowerShell (Core) 7 edition (pwsh, version 7.x, based on .NET (Core)),

    have separate execution policies.

    Each edition has its own SDK for hosting PowerShell in a custom application.

    The execution policies of the respective stand-alone edition apply to SDK projects too, with the exception of the LocalMachine policy of a stand-alone PowerShell 7 installation (note that while Windows PowerShell is guaranteed to be present on an given machine, the install-on-demand PowerShell 7 is not).

    However, the best solution is not to rely on a preconfigured execution policy to begin with, and instead, as part of your project, use an SDK method to set the execution policy dynamically, for the current process only, as follows (this is the equivalent of using Set-ExecutionPolicy -Scope Process RemoteSigned -Force) - this makes your application work predictably, irrespective of whether a stand-alone PowerShell 7 installation is also present and, if so, irrespective of what its configuration is:

      // Create an initial default session state.
      var iss = InitialSessionState.CreateDefault2();
    
      // Set its script-file execution policy (for the current session (process) only).
      iss.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.RemoteSigned;
    
      // Create a PowerShell instance with a runspace based on the 
      // initial session state and run a sample script to show
      // that the process-level policy took effect.
      using (PowerShell ps = PowerShell.Create(iss))
      {
        Console.WriteLine(
          ps.AddScript(@"Get-ExecutionPolicy -List | Out-String").Invoke()[0]
        );
      }