Search code examples
c#powershellasp.net-core-webapiasp.net-core-7.0

Getting "Running scripts is disabled on this system." in ASP.NET Core Web API


I am trying to run PowerShell script from ASP.NET Core Web API. Please see the environment details below:

.NET SDKs installed:
  3.1.403 [C:\Program Files\dotnet\sdk]
  7.0.200 [C:\Program Files\dotnet\sdk]
  7.0.201 [C:\Program Files\dotnet\sdk]
  7.0.306 [C:\Program Files\dotnet\sdk]
  7.0.400 [C:\Program Files\dotnet\sdk]
  8.0.100-preview.3.23178.7 [C:\Program Files\dotnet\sdk]
  8.0.101 [C:\Program Files\dotnet\sdk]

The API runs on .NET 7; here is the .csproj file:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.PowerShell.SDK" Version="7.3.9" />
  </ItemGroup>

The API code:

using System.Diagnostics;
using System.Management.Automation;
using System.Management.Automation.Runspaces;

public static ScriptResult Run2(string filePath, Dictionary<string,string> parameters)
{
    string myScript = File.ReadAllText(filePath);
    
    ScriptResult scriptResult = new ScriptResult();

    using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create())
    {
        ps.AddScript(myScript);

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

        var result = ps.Invoke();

        if (ps.HadErrors)
        {
            string errorMessage = "";

            foreach (var error in ps.Streams.Error)
            {
                errorMessage += error.ToString() + "\n"; <-- This is where I am getting the error message
            }

            throw new Exception(errorMessage);
        }

        foreach (var PSObject in result)
        {
            var x = PSObject.ToString();
        }
    }
    
    return scriptResult;
}

Output of Get-ExecutionPolicy -List:

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

Note: The script contains following line:

Import-Module MicrosoftTeams

##script to get token##

Connect-MicrosoftTeams -AccessTokens @("$graphToken", "$teamsToken") | Out-Null

$getUserSettings = Get-CSOnlineUser -Identity $UserEmail -ErrorAction 'stop'

$output = @{
  UserConfig = $getUserSettings
}

$output | ConvertTo-JSON

Write-Output $output

Output of PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.19041.4046
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.19041.4046
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

Getting error:

File C:\Program Files\WindowsPowerShell\Modules\MicrosoftTeams\5.5.0\MicrosoftTeams.psm1 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.


Solution

  • Joel Coehoorn's helpful answer provides a key part of the puzzle:

    • The implication is that the ASP.NET application runs with a different user identity than the one that shows Unrestricted as its current-user policy, and that that different user's current-user policy is either explicitly set to Restricted or - more likely - not defined (which Get-ExecutionPolicy -List reports as Undefined).

    • In the latter case, the effective execution policy is defined by the LocalMachine scope; if the latter is Undefined too, the default is Restricted, preventing script-file execution - this is what you saw.

      • Note: If the execution policy is defined by GPOs (MachinePolicy or UserPolicy), it can not be overridden by other means.
      • For a comprehensive overview of PowerShell's execution policies, see this answer.
    • In a PowerShell (Core) 7+ SDK project - unlike one for Windows PowerShell - a stand-alone PowerShell 7+ installation's LocalMachine policy is not visible to the SDK project, so the value of the LocalMachine policy when run from a stand-alone PowerShell 7+ session is irrelevant (as are Windows PowerShell's execution policies, which are entirely separate). However, the CurrentUser policy from a standalone PowerShell 7+ installation is seen and honored.[1]

    • The best solution is not to rely on a preconfigured execution policy to begin with, and instead 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 Bypass -Force):

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

    [1] Windows PowerShell stores its (non-GPO) execution policies in a fixed location in the registry, whereas PowerShell 7+ uses powershell.config.json files. For the LocalMachine scope, this file is stored alongside the PowerShell executable, which for SDK projects is not the home directory of any stand-alone PowerShell 7+ installation (which may or may not be present, and can hypothetically be installed anywhere), but the directory of the SDK project's own executable, as reflected in the PSHOME environment variable. By contrast, the CurrentUser-scope powershell.config.json is seen and honored by SDK projects, because it is stored in an installation-independent, fixed directory relative to the user's home directory. Specifically, the CurrentUser policy is stored in "$([Environment]::GetFolderPath('MyDocuments'))/powershell/powershell.config.json", and the LocalMachine one in "$PSHOME\powershell.config.json" (with $PSHOME having different meanings in stand-alone PowerShell vs. SDK projects).
    Note that this means that while you could hypothetically bundle a powershell.config.json file with your project with a preconfigured policy, it could still be preempted by a CurrentUser policy from a stand-alone installation. Only the dynamic, per-process policy override shown in the next bullet point can prevent that (except, as noted, if GPOs control the execution policy).