Semaine dernière, un collègue me demande comment exécuter du PowerShell depuis C#. Plus exactement  depuis une page web afin de faciliter certaines actions d’administration SharePoint.

Préparation

Pour commencer, il faut utiliser la classe PowerShell du namespace System.Management.Automation. Vous pouvez récupérer la DLL System.Management.Automation.dllen passant par NuGet.

System.Management.Automation

System.Management.Automation (PowerShell 3.0)

Code

Exécuter du PowerShell

using (PowerShell ps = PowerShell.Create()){
   // construire la commande avec ps.AddX()
   // exécuter la commande avec ps.Invoke()
   // travailler avec le résultat ou l'erreur
}

 

// PS> Get-P¨rocess "svchost"
ps.AddCommand("Get-Process");
ps.AddArgument("svchost");
// equivalent à 
ps.AddCommand("Get-Process");
ps.AddParameter("Name", "svchost");
// ou encore 
ps.AddScript("Get-Process | ? { $_.ProcessName -like 'svchost' }");

 

// PS> Get-Process -Name "svchost" -ComputerName "GLA-PC"
// version 1
ps.AddCommand("Get-Process");
ps.AddParameter("Name", "svchost");
ps.AddParameter("ComputerName", "GLA-PC");

// version 2
ps.AddCommand("Get-Process");
Dictionary<string, string> param = new Dictionary<string, string>();
param.Add("Name", "svchost");
param.Add("ComputerName", "GLA-PC");
ps.AddParameters(param);
// PS> Get-Process | Sort-Object -Descending id
ps.AddCommand("Get-Process");
ps.AddCommand("Sort-Object");
ps.AddParameter("descending");
ps.AddArgument("id");

La logique par défaut de la classe consiste à lier les commandes entre elles par des pipes. Si vous voulez exécuter des commandes sans lien entre elles, il faut utiliser PowerShell.AddStatement() qui n’est disponible que dans Automation pour Powershell 3.0

ps.AddScript("Add-PSSnapin Microsoft.SharePoint.PowerShell");
ps.AddStatement().AddScript("Get-SPUser –Identity ‘AD\GLA’ –Web ‘http://www.contoso.com’) | % { $_.DisplayName}");

équivalent à

Add-PSSnapin Microsoft.SharePoint.PowerShell
Get-SPUser –Identity ‘AD\GLA’ –Web ‘http://www.contoso.com’) | % { $_.DisplayName};

Ensuite on appelle Powershell.Invoke() pour une exécution synchrone ou PowerShell.BeginInvoke() pour une exécution asynchrone.

var results = ps.Invoke();

Gérer les résultats

Les résultats sont contenus dans une collection de PSObject. Vous pouvez ensuite les parcourir et les manipuler pour réaliser votre dessein.

if (results.Count &gt; 0){
   foreach (var psObject in results){
      System.Console.WriteLine(psObject.BaseObject.ToString());
      System.Console.WriteLine(psObject.Members[“member”].Value);
   }
}

Gérer les erreurs

Vous pourrez trouver les messages, warning et erreurs en utilisant la propriété PowerShell.Streams. Si vous appelez plusieurs fois PowerShell.Invoke(), pensez à nettoyer les flux avant chaque appel à l’aide de Clear() (exemple : ps.Streams.Error.Clear())

if (ps.Streams.Error.Count &gt; 0){
   System.Console.WriteLine("ERROR :");
   Collection&lt;ErrorRecord&gt; errors = ps.Streams.Error.ReadAll();
   if (errors != null || errors.Count &gt; 0){
      foreach (ErrorRecord er in errors){
         System.Console.WriteLine(er.Exception.ToString());
      }
   }
}

Configurer l’environnement

A ce stade de l’article, vous pouvez exécuter du PowerShell depuis C#. C’est bien mais d’une utilité relative (par rapport à un script ou un module). Par contre depuis asp.net – vous en conviendrez – cela prend plus de sens.

Quelques messages et exceptions qui peuvent survenir :

Dynamic operations can only be performed in homogenous AppDomain.

An exception of type ‘System.TypeInitializationException’ occurred in System.Management.Automation.dll but was not handled in user code

Additional information: The type initializer for ‘System.Management.Automation.SessionStateScope’ threw an exception.

Pour résoudre ce problème, vous devez modifier le fichier web.config du site qui execute la page. Trouver la ligne contenant legacyCasModel et passer la valeur à false comme ci-après :

<trust level="Full" originUrl="" legacyCasModel="false" />

Si vous travaillez avec un timerjob SharePoint, il vous faudra éditer le fichier HIVE\BIN\OWSTIMER.EXE.CONFIG (source : SoftLanding.ca)

<NetFx40_LegacySecurityPolicy enabled="false" />

Références:

MSDN – System.Management.Automation.PowerShell

NuGet.org – System.Management.Automation

NuGet.org – System.Management.Automation (PowerShell 3.0)