如何解决'System.Management.Automation.CmdletInvocationException'问题

时间:2010-04-14 09:20:56

标签: powershell msbuild

有谁知道如何最好地确定此异常的具体根本原因?

考虑一个WCF服务,该服务应该使用Powershell 2.0远程处理在远程计算机上执行MSBuild。在这两种情况下,脚本环境都在进程中调用(通过C#用于Powershell,通过Powershell用于MSBuild),而不是“shelling-out” - 这是一个特定的设计决策,以避免命令行地狱以及启用传递实际对象进入Powershell脚本。

调用MSBuild的Powershell脚本的简化版本如下所示:

function Run-MSBuild
{
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Build.Engine")

    $engine = New-Object Microsoft.Build.BuildEngine.Engine
    $engine.BinPath = "C:\Windows\Microsoft.NET\Framework\v3.5"

    $project = New-Object Microsoft.Build.BuildEngine.Project($engine, "3.5")
    $project.Load("deploy.targets")
    $project.InitialTargets = "DoStuff"

    # Process the input object
    while ($input.MoveNext())
    {
        # Set MSBuild Properties & Item
    }


    # Optionally setup some loggers (have also tried it without any loggers)
    $consoleLogger = New-Object Microsoft.Build.BuildEngine.ConsoleLogger
    $engine.RegisterLogger($consoleLogger)

    $fileLogger = New-Object Microsoft.Build.BuildEngine.FileLogger
    $fileLogger.Parameters = "verbosity=diagnostic"
    $engine.RegisterLogger($fileLogger)


    # Run the build - this is the line that throws a CmdletInvocationException
    $result = $project.Build()

    $engine.Shutdown()
}

从PS命令提示符运行上述脚本时,一切正常。但是,一旦从C#执行脚本,它就会失败并出现上述异常。

用于调用Powershell的C#代码如下所示(为简单起见,删除了远程处理功能):

// Build the DTO object that will be passed to Powershell
dto = SetupDTO()

RunspaceConfiguration runspaceConfig = RunspaceConfiguration.Create();
using (Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfig))
{
    runspace.Open();

    IList errors;
    using (var scriptInvoker = new RunspaceInvoke(runspace))
    {
        // The Powershell script lives in a file that gets compiled as an embedded resource
        TextReader tr = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("MyScriptResource"));
        string script = tr.ReadToEnd();

        // Load the script into the Runspace
        scriptInvoker.Invoke(script);

        // Call the function defined in the script, passing the DTO as an input object
        var psResults = scriptInvoker.Invoke("$input | Run-MSBuild", dto, out errors);
    }
}

注意:Invoke()方法的重载允许您传入一个IEnumerable对象,它负责在Powershell变量'$ input'中实例化一个枚举器 - 然后通过管道传递给脚本。以下是一些支持链接:

假设问题与MSBuild输出Powershell运行空间无法解决的问题有关,我还尝试了以下变体来调用第二个.Invoke():

var psResults = scriptInvoker.Invoke("$input | Run-MSBuild | Out-String", dto, out errors);
var psResults = scriptInvoker.Invoke("$input | Run-MSBuild | Out-Null", dto, out errors);
var psResults = scriptInvoker.Invoke("Run-MSBuild | Out-String");
var psResults = scriptInvoker.Invoke("Run-MSBuild | Out-String");
var psResults = scriptInvoker.Invoke("Run-MSBuild | Out-Null");
var psResults = scriptInvoker.Invoke("Run-MSBuild");

请注意,无论是否使用输入对象,基本问题仍然存在。

我还看过使用自定义PSHost(基于此示例:http://blogs.msdn.com/daiken/archive/2007/06/22/hosting-windows-powershell-sample-code.aspx),但在调试期间,我无法看到任何“有趣”的调用。

Stackoverflow的优点和好处是否有可能拯救我理智的洞察力?

1 个答案:

答案 0 :(得分:2)

我可以使用以下代码,但是我得到一个警告,MSBUILD引擎想要在STA线程上运行。不幸的是,PowerShell引擎创建的用于执行脚本的线程是MTA。也就是说,我的小样本有效:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation;
using System.Collections;

namespace RunspaceInvokeExp
{
    class Program
    {
        static void Main()
        {
            var script = @"
function Run-MSBuild 
{ 
    [System.Reflection.Assembly]::LoadWithPartialName(""Microsoft.Build.Engine"") 

    $engine = New-Object Microsoft.Build.BuildEngine.Engine 
    $engine.BinPath = ""C:\Windows\Microsoft.NET\Framework\v3.5"" 

    $project = New-Object Microsoft.Build.BuildEngine.Project($engine, ""3.5"") 
    $project.Load(""deploy.targets"") 
    $project.InitialTargets = ""DoStuff"" 

    # Process the input object 
    while ($input.MoveNext()) 
    { 
        # Set MSBuild Properties & Item 
    } 

    # Optionally setup some loggers (have also tried it without any loggers) 
    $consoleLogger = New-Object Microsoft.Build.BuildEngine.ConsoleLogger 
    $engine.RegisterLogger($consoleLogger) 

    $fileLogger = New-Object Microsoft.Build.BuildEngine.FileLogger 
    $fileLogger.Parameters = ""verbosity=diagnostic"" 
    $engine.RegisterLogger($fileLogger) 

    # Run the build - this is the line that throws a CmdletInvocationException 
    $result = $project.Build() 

    $engine.Shutdown() 
} 
";
            using (var invoker = new RunspaceInvoke())
            {
                invoker.Invoke(script);
                IList errors;
                Collection<PSObject> results = invoker.Invoke(@"$input | Run-MSBuild", new[] {0}, out errors);
                Array.ForEach<PSObject>(results.ToArray(), Console.WriteLine);
            }
        }
    }
}

您的C#代码哪一行失败?此外,您可以发布异常中的一些细节。您可以通过执行以下操作来解决MTA线程问题:

using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;

namespace RunspaceInvokeExp
{
    class Program
    {
        [STAThread]
        static void Main()
        {
            var script = @"
function Run-MSBuild 
{ 
    [System.Reflection.Assembly]::LoadWithPartialName(""Microsoft.Build.Engine"") 

    $engine = New-Object Microsoft.Build.BuildEngine.Engine 
    $engine.BinPath = ""C:\Windows\Microsoft.NET\Framework\v3.5"" 

    $project = New-Object Microsoft.Build.BuildEngine.Project($engine, ""3.5"") 
    $project.Load(""deploy.targets"") 
    $project.InitialTargets = ""DoStuff"" 

    # Process the input object 
    while ($input.MoveNext()) 
    { 
        # Set MSBuild Properties & Item 
    } 

    # Optionally setup some loggers (have also tried it without any loggers) 
    $consoleLogger = New-Object Microsoft.Build.BuildEngine.ConsoleLogger 
    $engine.RegisterLogger($consoleLogger) 

    $fileLogger = New-Object Microsoft.Build.BuildEngine.FileLogger 
    $fileLogger.Parameters = ""verbosity=diagnostic"" 
    $engine.RegisterLogger($fileLogger) 

    # Run the build - this is the line that throws a CmdletInvocationException 
    $result = $project.Build() 

    $engine.Shutdown() 
} 

Run-MSBuild
";

            Runspace runspace = RunspaceFactory.CreateRunspace();
            Runspace.DefaultRunspace = runspace;
            runspace.Open();

            EngineIntrinsics engine = runspace.SessionStateProxy.
                GetVariable("ExecutionContext") as EngineIntrinsics;
            ScriptBlock scriptblock = 
                engine.InvokeCommand.NewScriptBlock(script);
            Collection<PSObject> results = scriptblock.Invoke(new[] { 0 });
            Array.ForEach<PSObject>(results.ToArray(), Console.WriteLine);

            runspace.Close(); // Really should be in a finally block
        }
    }
}