How to read powershell manifest file (.psd1) using c# - c#

I am trying to access manifest details for a custom PowerShell module that has the manifest file stored along with the module(psm1) file in my directory structure.
What is the best way to access the manifest details like Description, GUID etc?

A psd1 file is a valid PowerShell script, so it's best to let PowerShell parse the file.
The simplest way is to use the Test-ModuleManifest cmdlet. From C#, that would look something like:
using (var ps = PowerShell.Create())
{
ps.AddCommand("Test-ModuleManifest").AddParameter("Path", manifestPath);
var result = ps.Invoke();
PSModuleInfo moduleInfo = result[0].BaseObject as PSModuleInfo;
// now you can look at the properties like Guid or Description
}
Other approaches cannot handle the complexities of parsing PowerShell, e.g. it would be easy to incorrectly handle comments or here strings when trying to use a regex.

Add a reference to System.Management.Automation. Then, use the following code to get a Hashtable from the .psd1 file.
static void Main(string[] args)
{
PowerShell ps = PowerShell.Create();
string psd = "C:\\Users\\Trevor\\Documents\\WindowsPowerShell\\Modules\\ISESteroids\\ISESteroids.psd1";
ps.AddScript(String.Format("Invoke-Expression -Command (Get-Content -Path \"{0}\" -Raw)", psd));
var result = ps.Invoke();
Debug.WriteLine(((Hashtable)result[0].ImmediateBaseObject)["Description"]);
}

Related

How to retrieve value of pwd from powershell in c# code

I was looking for the answer at least 3 hours, but without success.
Everywhere are just pieces of code and I have no idea how to connect them together.
What I want to achieve:
I am creating dotnet-tool in which I need to process current working directory (value which is printed using pwd command in PS).
Dotnet tool will be installed in default directory C:\Users\Samuel\.dotnet\.store\myTool... but command can be invoked in any directory in PS using dotnet tool run myTool.
For example:
In PS I am in: C:\Users\Samuel\AxisRepos> and I run dotnet tool run myTool
In this case I want to retrieve C:\Users\Samuel\AxisRepos in C# code to find out, in which directory command was invoked.
So simply put, I want to do something like this in my dotnet tool:
class Program
{
static void Main(string[] args)
{
var pwd = GetPwdFromPowerShell();
}
static string GetPwdFromPowerShell()
{
string pwd = "";
// Retrieve current working directory from PS in which dotnet tool was invoked
return pwd;
}
}
pwd is just an alias for Get-Location which shows the current directory...
From c# you can use the built-in .net class System.IO.Directory specifically the method GetCurrentDirectory
So just update your code to:
static string GetPwd()
{
return System.IO.Directory.GetCurrentDirectory();
}
And from powershell this will have the same results:
[System.IO.Directory]::GetCurrentDirectory()

Save file on server using powershell

I am trying to create xml file in C# and save that newly created file in local machine using powershell. new file is created at local but content is not saved.
I am creating simple xml file in C# as follows
XDocument doc = new XDocument(new XElement("body",
new XElement("level1",
new XElement("level2", "text"),
new XElement("level2", "other text"))));
and I am passing "doc" as parameter to powershell script and invoke powershell as follows
Dictionary<string, XDocument> parameters = new Dictionary<string, XDocument>() { { "VMConfigFile", doc } };
powershell.AddCommand("PowershellFunc").AddParameters(parameters);
Collection<PSObject> results = powershell.Invoke();
Collection<ErrorRecord> errors = powershell.Streams.Error.ReadAll();
powershell function as
function PowershellFunc
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true,
Position=0,
HelpMessage='Please Provide Config File')]
[ValidateNotNullOrEmpty()]
[xml]$VMConfigFile
)
try
{
$txt = $VMConfigFile
$Session = New-PSSession 127.0.0.1
Invoke-Command -Session $Session -ScriptBlock { Param($Txt) New-Item -Path c:\test\newFile.xml -Value $txt } -ArgumentList $txt
#Get-PSSession | Remove-PSSession
Write-Output "`file save successfully."
}
catch
{
Throw $_.exception.message
}
}
file is created after script run but it contains namespace("System.Xml.XmlDocument") only not file content.
I have also, tried to find out question related to my problem but most questiones are belong to read xml file from given path.
Question :-
How to pass xml file as parameter to powershell?(What I have did is it right?)
How to get that file in $txt(in powershell variable)? (I thing i am wrong here but i am not sure how to do that)
Is there any better way to do this?(best practices)
Since you're getting the Namespace as the output, it looks like the document is making it to Powershell fine.
New-Item just calls the ToString method of the variable and sticks that into the file. For a lot of objects in Powershell, that's just the type or namespace of the object. Only really simple objects will actually output the way you expect. You should use the .Save() method within the XMLDocument type to export it properly. Export-Clixml is an option too.
XMLDocument .Save(): https://learn.microsoft.com/en-us/dotnet/api/system.xml.xmldocument.save?view=netcore-3.1#System_Xml_XmlDocument_Save_System_String_
Export-Clixml Docs: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/export-clixml?view=powershell-7
It's not detailed here why you're starting with C# and then switching to Powershell, but the two are very closely intertwined and System.Xml.XmlDocument is a .Net namespace that's accessible in both languages. Unless there is a necessary reason, it might be easier to keep all of this in just one language.

Launching powershell scripts (with parameters) from c# app

I'm trying to make a simple (or so i thought) app that will make it easier to launch .ps1 scripts, so that non-powershell savvy users can use them.
Here is how its supposed to look like
Now, i managed to figure out one part about running scripts:
private string RunPowershell_1(string skripta)
{
StringBuilder stringBuilder = new StringBuilder();
foreach (string str in PowerShell.Create().AddScript(skripta).AddCommand("Out-String").Invoke<string>())
{
stringBuilder.AppendLine(str);
}
return stringBuilder.ToString();
}
But i would normally run scripts that require parameters, so i would like to be able to read list of parameters from the script i import, assign value to them and then run the scrip (output should go to either txtPreview or to a file).
Is there a way to do this?
If there is another (better) approach to this I'm all ears.
You can use this powershell command to get the list of parameters.
(get-command get-netadapter).Parameters
And this is how you can read its output in C#:
using (PowerShell ps = PowerShell.Create())
{
ps.AddScript("...");
Collection<PSObject> output = ps.Invoke();
}

Trouble with calling PS script (with parameter) from C#

I am trying to setup a simple .aspx web page that will accept a user's input of a string (later, more than one string) and use that string as the parameter value for a Powershell script.
The PS script looks like this right now:
[CmdletBinding()]
param (
[string] $ServiceName
)
$ServiceName | out-file c:\it\test.txt
$ServiceName | Out-String
The C# code looks like this:
var shell = PowerShell.Create();
// Add the script to the PowerShell object
shell.Commands.AddScript("C:\\it\\test.ps1 -ServiceName BITS");
// Execute the script
var results = shell.Invoke();
When I run that, I get "BITS" written to the test.txt file. What I need to do now, is setup the application to call the script, passing in the "ServiceName" parameter. I found this: Call PowerShell script file with parameters in C# and tried the following code:
PowerShell ps = PowerShell.Create();
ps.AddScript(#"c:\it\test.ps1").AddParameter("ServiceName", "BITS");
var results = ps.Invoke();
In this case, the script was called and the test.txt file was created, but the value (BITS) was not written to the file. What am I missing here? Why isn't the parameter being passed to the script?
Thanks.
I ended up using
var ps = #"C:\it\test.ps1";
processInfo = new ProcessStartInfo("powershell.exe", "-File " + ps + " -ServiceName BITS);
I don't like this as much, but it works. shrug
Here are three possible solutions for future readers including me:
using (PowerShell ps = PowerShell.Create())
{
//Solution #1
//ps.AddCommand(#"C:\it\test.ps1", true).AddParameter("ServiceName", "BITS");
//Solution #2
//ps.AddScript(#"C:\it\test.ps1 -ServiceName 'BITS'", true);
//Solution #3
ps.AddScript(File.ReadAllText(#"C:\it\test.ps1"), true).AddParameter("ServiceName", "BITS");
Collection<PSObject> results = ps.Invoke();
}
I haven't seen solution #3 documented anywhere else, though I got the idea for it from https://blogs.msdn.microsoft.com/kebab/2014/04/28/executing-powershell-scripts-from-c/

c# how to import a powershell module from a string

I'm using c# and I want to use import-module to import a powershell script. However I don't want to have a .psm1 file on disk. I want to have it hardcoded on my code, like in a string and then import it.
Is that possible?
All the example I can find are something like:
pipeline.Commands.Add("Import-Module");
var command = pipeline.Commands[0];
command.Parameters.Add("Name", #"G:\PowerShell\PowerDbg.psm1")
or something like:
var ps = PowerShell.Create(myRS);
ps.Commands.AddCommand("Import-Module").AddArgument(#"g:\...\PowerDbg.psm1")
ps.Invoke()
However as I said above I don't want to read a file from disk. I want it hardcoded to avoid multiple files. I want everything on an exe and that's it. I couldn't find a way, any help is appreciated.
The reason I want to use import-module is because after importing the module I want to do something like:
get-command -module <whatever>
and get a list of all its functions.
Any other way to list functions from a script might be helpful too.
Thanks.
You're looking for New-Module; it does exactly what you're asking for.
From the TechNet page (paraphrased):
New-Module -ScriptBlock {
$SayHelloHelp="Type 'SayHello', a space, and a name."
function SayHello ($name) {
"Hello, $name"
}
Export-ModuleMember -function SayHello -Variable SayHelloHelp
} -Name PowerDbg
C# Example (not tested):
string moduleContents = #"...";
pipeline.Commands.Add("New-Module");
var command = pipeline.Commands[0];
command.Parameters.Add("ScriptBlock", moduleContents);
command.Parameters.Add("Name", "PowerDbg");
pipeline.Invoke();

Categories