从C#调用PowerShell cmdlet会引发CommandNotFoundException [英] Calling PowerShell cmdlet from C# throws CommandNotFoundException

查看:212
本文介绍了从C#调用PowerShell cmdlet会引发CommandNotFoundException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试从C#调用Add-AppxPackage cmdlet.我在从C#运行PowerShell的位置上找到了MSDN文章.代码.我已经引用了System.Management.Automation程序集,并尝试了以下代码段,当尝试调用powerShell.Invoke()时,所有这些代码段均导致相同的异常:

未处理System.Management.Automation.CommandNotFoundException

"Add-AppxPackage"一词无法识别为cmdlet的名称, 功能,脚本文件或可操作程序.检查拼写 名称,或者如果包含路径,请验证路径是否正确,以及 再试一次.

代码段1:

var powerShell = PowerShell.Create();
powerShell.AddCommand(string.Format("Add-AppxPackage '{0}'", appxFilePath));

foreach (PSObject result in powerShell.Invoke())
{
    Console.WriteLine(result);
}

我理解为什么这行不通,因为我不应该在AddCommand()函数中提供参数.

代码段2:

var powerShell = PowerShell.Create();
powerShell.AddCommand("Add-AppxPackage");
powerShell.AddParameter("Path", appxFilePath);

foreach (PSObject result in powerShell.Invoke())
{
    Console.WriteLine(result);
}

代码段3:

var powerShell = PowerShell.Create();
powerShell.AddCommand("Add-AppxPackage");
powerShell.AddArgument(appxFilePath);

foreach (PSObject result in powerShell.Invoke())
{
    Console.WriteLine(result);
}

我的C#项目的目标是.Net 4.5,如果我执行powerShell.AddCommand("Get-Host"),它将起作用,并且返回的Version是4.0.在PowerShell的v3.0中添加了Add-AppxPackage,因此该命令肯定应该存在,并且如果我从Windows PowerShell命令提示符下手动运行此命令,它将正常工作.

有什么主意我在这里做错了吗?任何建议表示赞赏.

-更新-

我发现了这篇文章

它不会引发异常,但是也不会安装Metro应用程序,并且从powerShell.Invoke()返回的结果"为空,因此我仍然茫然不知所措…… >

-更新2-

因此,我决定尝试创建一个新的PowerShell进程来运行命令,因此尝试了此操作:

Process.Start(new ProcessStartInfo("PowerShell", string.Format("-Command Add-AppxPackage '{0}'; $key = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyUp')", appxFilePath)));

,但仍会引发与Add-AppxPackage无法识别的cmdlet相同的错误.

答案

如果您遵循robert.westerlund的答案上的长长的注释线程,您将看到由于某种原因从Visual Studio运行/启动时,PowerShell不包括直接从PowerShell命令提示符运行时所执行的所有PSModulePaths. ,因此不存在许多模块.解决方案是使用以下命令找到我需要的模块(在我的情况下为appx模块)的绝对路径:

(Get-Module appx -ListAvailable).Path

然后在尝试调用其cmdlet之一之前导入该模块.这是对我有用的C#代码:

var powerShell = PowerShell.Create();
powerShell.AddScript(string.Format(@"Import-Module 'C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\Appx\Appx.psd1'; Add-AppxPackage '{0}'", appxFilePath));
var results = powerShell.Invoke();

更新后的答案

您可以从看到在我打开的另一篇文章中,问题出在Visual Studio扩展程序(在我的情况下为StudioShell)中,该错误导致并非所有PSModulePaths都被加载.卸载该扩展程序后,所有模块均已正确加载,不再需要手动导入模块.

解决方案

在PowerShell中,终止错误(停止执行)和非终止错误(仅写入错误流)之间是有区别的.

如果要在自己的函数中创建一个非终止错误,只需使用Write-Error cmdlet.如果要创建终止错误,请使用Throw关键字.如果运行Get-Help Write-ErrorGet-Help about_ThrowGet-Help about_Try_Catch_Finally,则可以阅读有关这些概念的更多信息.

Add-AppxPackage与不存在的程序包一起使用是一个非终止错误,因此将被写入错误流,但是不会引发执行暂停异常.以下代码尝试添加一个不存在的程序包,然后将错误写入控制台.

var powerShell = PowerShell.Create();
powerShell.AddScript("Add-AppxPackage NonExistingPackageName");

// Terminating errors will be thrown as exceptions when calling the Invoke method. 
// If we want to handle terminating errors, we should place the Invoke call inside a try-catch block.
var results = powerShell.Invoke();

// To check if a non terminating error has occurred, test the HadErrors property
if (powerShell.HadErrors)
{
    // The documentation for the Error property states that "The command invoked by the PowerShell 
    // object writes information to this stream whenever a nonterminating error occurs."
    foreach (var error in powerShell.Streams.Error)
    {
        Console.WriteLine("Error: " + error);
    }
}
else
{
    foreach(var package in results)
    {
        Console.WriteLine(package);
    }
}

I'm trying to call the Add-AppxPackage cmdlet from C#. I found the MSDN article on running PowerShell from C# code. I have referenced the System.Management.Automation assembly and have tried the following code snippets, all of which result in the same exception when trying to call powerShell.Invoke():

System.Management.Automation.CommandNotFoundException was unhandled

The term 'Add-AppxPackage' 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.

Snippet 1:

var powerShell = PowerShell.Create();
powerShell.AddCommand(string.Format("Add-AppxPackage '{0}'", appxFilePath));

foreach (PSObject result in powerShell.Invoke())
{
    Console.WriteLine(result);
}

I understand why this doesn't work, since I shouldn't be providing parameters in the AddCommand() function.

Snippet 2:

var powerShell = PowerShell.Create();
powerShell.AddCommand("Add-AppxPackage");
powerShell.AddParameter("Path", appxFilePath);

foreach (PSObject result in powerShell.Invoke())
{
    Console.WriteLine(result);
}

Snippet 3:

var powerShell = PowerShell.Create();
powerShell.AddCommand("Add-AppxPackage");
powerShell.AddArgument(appxFilePath);

foreach (PSObject result in powerShell.Invoke())
{
    Console.WriteLine(result);
}

My C# project targets .Net 4.5, and if I do powerShell.AddCommand("Get-Host") it works and the Version it returns back is 4.0. Add-AppxPackage was added in v3.0 of PowerShell, so the command should definitely exist, and it works fine if I manually run this command from the Windows PowerShell command prompt.

Any ideas what I am doing wrong here? Any suggestions are appreciated.

-- Update --

I found this post and this one, and realized there is a AddScript() function, so I tried this:

Snippet 4:

var powerShell = PowerShell.Create();
powerShell.AddScript(string.Format("Add-AppxPackage '{0}'", appxFilePath));

var results = powerShell.Invoke();
foreach (PSObject result in results)
{
    Console.WriteLine(result);
}

And it does not throw an exception, but it also doesn't install the metro app, and the "results" returned from powerShell.Invoke() are empty, so I'm still at a loss...

-- Update 2 --

So I decided that I would try just creating a new PowerShell process to run my command, so I tried this:

Process.Start(new ProcessStartInfo("PowerShell", string.Format("-Command Add-AppxPackage '{0}'; $key = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyUp')", appxFilePath)));

but it still throws the same error that Add-AppxPackage is not a recognized cmdlet.

ANSWER

If you follow the long comment thread on robert.westerlund's answer, you will see that for some reason when running/launched from Visual Studio, PowerShell was not including all of the PSModulePaths that it does when running straight from a PowerShell command prompt, so many modules are not present. The solution was to find the absolute path of the module that I needed (the appx module in my case) using:

(Get-Module appx -ListAvailable).Path

And then import that module before trying to call one of its cmdlets. So this is the C# code that worked for me:

var powerShell = PowerShell.Create();
powerShell.AddScript(string.Format(@"Import-Module 'C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\Appx\Appx.psd1'; Add-AppxPackage '{0}'", appxFilePath));
var results = powerShell.Invoke();

UPDATED ANSWER

You can see from this other post I opened, that the problem was with a bug in a Visual Studio extension (in my case StudioShell) causing not all of the PSModulePaths to be loaded. After uninstalling that extension all of the modules were loaded correctly and I no longer needed to manually import the module.

解决方案

In PowerShell there is a difference between terminating errors (which stops the execution) and non-terminating errors (which are just written to the error stream).

If you want to create a non-terminating error in a function of your own, just use the Write-Error cmdlet. If you want to create a terminating error, use the Throw keyword. You can read more about these concepts if you run Get-Help Write-Error, Get-Help about_Throw and Get-Help about_Try_Catch_Finally.

Using the Add-AppxPackage with a non existing package is a non terminating error and will thus be written to the error stream, but no execution halting exception will be thrown. The following code tries to add a non existing package and then writes the error to the console.

var powerShell = PowerShell.Create();
powerShell.AddScript("Add-AppxPackage NonExistingPackageName");

// Terminating errors will be thrown as exceptions when calling the Invoke method. 
// If we want to handle terminating errors, we should place the Invoke call inside a try-catch block.
var results = powerShell.Invoke();

// To check if a non terminating error has occurred, test the HadErrors property
if (powerShell.HadErrors)
{
    // The documentation for the Error property states that "The command invoked by the PowerShell 
    // object writes information to this stream whenever a nonterminating error occurs."
    foreach (var error in powerShell.Streams.Error)
    {
        Console.WriteLine("Error: " + error);
    }
}
else
{
    foreach(var package in results)
    {
        Console.WriteLine(package);
    }
}

这篇关于从C#调用PowerShell cmdlet会引发CommandNotFoundException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆