Skip to content

Scripts using -Parallel fail when run using Microsoft.PowerShell.SDK and a publish runtime flag #13132

@normj

Description

@normj

We use the Microsoft.PowerShell.SDK (7.0.2) NuGet package in a .NET Core 3.1 application to execute PowerShell 7 scripts in a Linux environment. We have found when the .NET application is published with the runtime linux-x64 to reduce the package size, scripts that use the ForEach-Object -Parallel ... fail with the following error message.

[Error] - The term 'Write-Host' is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

If the .NET application is packaged without specifying runtime the script runs correctly. Also if the ForEach-Object command does not use the -Parallel parameter the script runs correctly.

Steps to reproduce

Here is the test PowerShell script attempting to run through the .NET Application.

$SharedVariable = "Hello Shared Variable"
@(1..100) | ForEach-Object -Parallel { 
    $i = $_
    Write-Host "Running against: $i for SharedVariable: $($using:SharedVariable)"
}

Here is a trimmed down version of the .NET Core 3.1 application that is used for running the PowerShell script.

using System;
using System.IO;
using System.Management.Automation;

namespace PowerShellScriptRunner
{
    class Program
    {
        static Exception _lastException;

        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                Console.Error.WriteLine("The first argument should the path to the script to run.");
                System.Environment.Exit(-1);
            }
            else if (!File.Exists(args[0]))
            {
                Console.Error.WriteLine($"Script {args[0]} can not be found.");
                System.Environment.Exit(-2);
            }

            var executingScript = File.ReadAllText(args[0]);
            Console.WriteLine("Executing script:");
            Console.WriteLine(executingScript);
            var ps = PowerShell.Create();
            SetupStreamHandlers(ps);

            ps.AddScript(executingScript);

            var output = new PSDataCollection<PSObject>();
            var asyncResult = ps.BeginInvoke<PSObject, PSObject>(null, output);

            while (!asyncResult.IsCompleted)
            {
                asyncResult.AsyncWaitHandle.WaitOne(500);
            }
        }

        private static void SetupStreamHandlers(PowerShell ps)
        {

            Func<string, EventHandler<DataAddingEventArgs>> _loggerFactory = (prefix) =>
            {
                EventHandler<DataAddingEventArgs> handler = (sender, e) =>
                {
                    var message = e?.ItemAdded?.ToString();

                    LogMessage(prefix, message);

                    var errorRecord = e?.ItemAdded as ErrorRecord;
                    if (errorRecord?.Exception != null)
                    {
                        _lastException = errorRecord.Exception;
                    }
                };
                return handler;
            };

            ps.Streams.Verbose.DataAdding += _loggerFactory("Verbose");
            ps.Streams.Information.DataAdding += _loggerFactory("Information");
            ps.Streams.Warning.DataAdding += _loggerFactory("Warning");
            ps.Streams.Error.DataAdding += _loggerFactory("Error");
        }

        private static void LogMessage(string prefix, string message)
        {
            if (string.IsNullOrEmpty(message))
            {
                return;
            }

            if (!string.IsNullOrEmpty(prefix))
            {
                message = $"[{prefix}] - {message}";
            }

            Console.WriteLine(message);
        }
    }
}

On a Linux environment publish the project using the following command

dotnet publish --configuration Release --self-contained false --runtime linux-x64

then execute the program

dotnet exec .\bin\Release\netcoreapp3.1\linux-x64\publish\PowerShellScriptRunner.dll <path-to-psscriptfile>

Script fails with the following error messages.

[Error] - The term 'Write-Host' is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

If i repeat the steps above but remove the --runtime linux-x64 switch there is no problem.

One theory I have is with the --runtime linux-x64 switch the assemblies from the publish\runtimes folder get flatten out at the root of the publish folder. But there is also the Modules in the publish\runtimes\unix\lib\netcoreapp3.1\Modules and maybe something about the modules not being direct children of the assemblies anymore is causing the failure.

On a side note the value --runtime win-x64 also doesn't seem to work either but for a different error. That error is

System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.Management.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'. The system cannot find the file specified.
File name: 'Microsoft.Management.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
   at System.Reflection.RuntimeAssembly.GetExportedTypes()
   at System.Reflection.Assembly.get_ExportedTypes()
   at System.Management.Automation.Runspaces.PSSnapInHelpers.GetAssemblyTypes(Assembly assembly, String name)
   at System.Management.Automation.Runspaces.PSSnapInHelpers.AnalyzeModuleAssemblyWithReflection(Assembly assembly, String name, PSSnapInInfo psSnapInInfo, PSModuleInfo moduleInfo, String helpFile, Dictionary`2& cmdlets, Dictionary`2& aliases, Dictionary`2& providers)
   at System.Management.Automation.Runspaces.PSSnapInHelpers.AnalyzePSSnapInAssembly(Assembly assembly, String name, PSSnapInInfo psSnapInInfo, PSModuleInfo moduleInfo, Dictionary`2& cmdlets, Dictionary`2& aliases, Dictionary`2& providers, String& helpFile)
   at System.Management.Automation.Runspaces.InitialSessionState.ImportPSSnapIn(PSSnapInInfo psSnapInInfo, PSSnapInException& warning)
   at System.Management.Automation.Runspaces.InitialSessionState.CreateDefault()
   at System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace(PSHost host)
   at System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace()
   at System.Management.Automation.PowerShell.Worker.CreateRunspaceIfNeededAndDoWork(Runspace rsToUse, Boolean isSync)

Which I can see the Microsoft.Management.Infrastructure.dll is missing in the publish folder but Microsoft.Management.Infrastructure.CimCmdlets.dll is there. This isn't my use case as I'm on Linux but maybe related somehow.

Expected behavior

Scripts using -Parallel argument execute successfully.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-Questionideally support can be provided via other mechanisms, but sometimes folks do open an issue to get aResolution-AnsweredThe question is answered.WG-Cmdlets-Corecmdlets in the Microsoft.PowerShell.Core moduleWG-DevEx-SDKhosting PowerShell as a runtime, PowerShell's APIs, PowerShell Standard, or development of modules

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions