2

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.

Community
  • 1
  • 1
deadlydog
  • 22,611
  • 14
  • 112
  • 118

1 Answers1

7

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);
    }
}
Robert Westerlund
  • 4,750
  • 1
  • 20
  • 32
  • You should be abe to use this to read the non terminating error you are most likely receiving when calling the `Add-AppxPackage` function. – Robert Westerlund Mar 19 '14 at 09:45
  • Thanks for the code, but it looks like this is a terminating error; powerShell.Invoke() throws a CommandNotFoundException, so it never hits any code after that (unless I wrap it in a try-catch). If I do wrap it in a try-catch then powerShell.HadErrors is true, but powerShell.Streams.Error is empty. The problem is not that the .appx file does not exist, but that it does not recognize the Add-AppxPackage command, even though it is using PowerShell v4.0 which does have this command. – deadlydog Mar 24 '14 at 22:11
  • In your `Snippet 4` you mention that no exception was thrown but no application was installed. That was the snippet I based my sample on. Has something changed; are you receiving an exception in that snippet too now? – Robert Westerlund Mar 24 '14 at 23:25
  • Ah, ok. I just ran it again using the code from Snippet 4 and you are right; when I run it this way it is a non-terminating error (i.e. no exception is thrown). powerShell.Streams.Error contains the same error message though that Add-AppxPackage is not a recognized cmdlet. – deadlydog Mar 24 '14 at 23:36
  • Windows 8.1 64-bit. Even if I replace my .appx filepath with an invalid path, like "C:\DoesNotExist.txt", the same errors are thrown that Add-AppxPackage is not recognized. – deadlydog Mar 24 '14 at 23:39
  • What do you get if you do `Get-Module AppX` (or including the `-ListAvailable` switch)? Does it find the module at all? – Robert Westerlund Mar 24 '14 at 23:41
  • And also, which PowerShell version does it state that you are running, if you check the `PSVersion` property on the `$PSVersionTable` variable (`Get-Host` is not interesting, the potentially interesting information lies in the `$PSVersionTable` variable)? – Robert Westerlund Mar 24 '14 at 23:45
  • Using 'powerShell.AddScript("$PSVersionTable.PSVersion");' returns back '4.0', but 'Get-Module appx' does not return back anything; but it does if I run it directly in a PowerShell command prompt. So it seems that the Appx module is not loaded for some reason, and using 'Import-Module Appx' returns an error message saying that no valid module file could be found. Also, using 'Get-Module -All' does not return anything back either, so it looks like none of the required modules are loaded. – deadlydog Mar 25 '14 at 00:01
  • 2
    If it does exist normally, then perhaps the problem is that you don't have the correct environment variables available? Could you compare the results of `(Get-Item Env:\PSModulePath).Value -Split ";"`? And also, could you try loading the AppX module using an absolute path (I think it should be at `Import-Module C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\Appx\Appx.psd1`, the path can be found by doing `(get-module appx -ListAvailable).Path` in the PowerShell command prompt where you could find the module in question) and see if that works? – Robert Westerlund Mar 25 '14 at 00:36
  • Looks like you are right; from code the only PSModulePath is the one in my user's documents folder, but from a regular powershell prompt it includes 6 different paths, one of which is `C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\`. Changing to import the module using the full absolute path and then using Add-AppxPackage worked :) Thanks so much! I wonder why when I run from VS 2013 it doesn't load all of the module paths though? I tried both running VS as admin and not, and get the same results, and $Env:UserProfile is the same in VS as in the regular PowerShell prompt. – deadlydog Mar 25 '14 at 15:52