Save file on server using powershell - c#

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.

Related

How to get output of PowerShell's Get-WmiObject in C#

I need to get which network interface is connected to which network. I found that this information is accessible in MSFT_NetConnectionProfile. Unfortunatelly I cannot access it directly from C# (I get ManagementException: Provider load failure on computer where it should run) but when I access it from PowerShell, it works. Then my idea is to run PowerShell command from C# but I cannot get the result.
using System.Management.Automation;
string command = "Get-WmiObject -Namespace root/StandardCimv2 -Class MSFT_NetConnectionProfile | Select-Object -Property InterfaceAlias, Name";
PowerShell psinstance = PowerShell.Create();
psinstance.Commands.AddScript(command);
var results = psinstance.Invoke();
foreach (var psObject in results)
{
/* Get name and interfaceAlias */
}
The code runs without errors but results are empty. I tried even adding Out-File -FilePath <path-to-file> with relative and absolute file path but no file was created. I even tried old >> <path-to-file> but without luck. When I added Out-String then there was one result but it was empty string.
When I tested the commands directly in PowerShell then it worked. Is there a way how to get it in C#?
The PS commands must be constructed in a builder-pattern fashion.
Additionally, in PS Core the Get-WmiObject has been replaced by the Get-CimInstance CmdLet.
The following snippet is working on my env:
var result = PowerShell.Create()
.AddCommand("Get-CimInstance")
.AddParameter("Namespace", "root/StandardCimv2")
.AddParameter("Class", "MSFT_NetConnectionProfile")
.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();

How to read powershell manifest file (.psd1) using 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"]);
}

Powershell Pipe and Foreach-Object in c#

I am invoking a get-msoluser cmdlet of office365 and i use the following cmdlet in powershell
Get-MsolUser -UserPrincipalName user#organization.onmicrosoft.com | ForEach-Object{ $_.licenses}
The output is a collection of licenses and i wanted the same script to be run in c#. so i have written the code as follows
private void displayLicenses(){
Command cmd = new Command("Get-MsolUser");
cmd.Parameters.Add("UserPrincipalName","user#organization.onmicrosoft.com");
Command cmd2 = new Command("ForEach-Object");
cmd2.Parameters.Add("$_.licenses.AccountSku");
Pipeline pipe = Office365Runspace.CreatePipeline();
pipe.Commands.Add(cmd);
pipe.Commands.Add(cmd2);
Console.WriteLine("Before invoking the pipe");
ICollection<PSObject> result = pipe.Invoke();
CheckForErrors(pipe);
Console.WriteLine("Executed command {0} + {1} with no error", cmd.CommandText, cmd2.CommandText);
foreach(PSObject obj in result){
foreach(PSPropertyInfo propInfo in obj.Properties){
Console.WriteLine(propInfo.Name+": "+propInfo.Value+" "+propInfo.MemberType);
}
}
}
But i still get an error on executing this function saying
Unhandled Exception:
System.Management.Automation.CommandNotFoundException: The term
'ForEach-Object' is not recognized as the name of a cmdlet, function,
scrip t file, or operable program. Check the spelling of the name, or
if a path was in cluded, verify that the path is correct and try
again.
I checked that my project has a reference to System.management.Automation.dll file that contains the ForEach-Object cmdlet.
I found the dll using this cmd in powershell
(Get-Command ForEach-Object).dll
Thanks,
Satya
I figured out the problem causing for the issue. It is due to the misconfigured runspace i created.
InitialSessionState initalState = InitialSessionState.Create();
initalState.ImportPSModule(new String[] { "msonline" });
//initalState.LanguageMode = PSLanguageMode.FullLanguage;
Office365Runspace = RunspaceFactory.CreateRunspace(initalState);
Office365Runspace.Open();
i was creating the initalstate with empty one,When i changed it to default one it worked fine.On creating the default one it includes all the modules that were obtained by default.
InitialSessionState initalState = InitialSessionState.CreateDefault();
it worked fine.
Thanks,
Satya
It sounds like your're trying to run that in the remote session at the Exchange server. Those are NoLanguage constrained sessions, meaning that you can only run the Exchange cmdlets in those sessions. If you want to use PowerShell core language cmdlets (like foreach-object), you have to do that in a local session and either use Import-PSSession to import the Exchange functions into your local session (implicit remoting) , or use Invoke-Command and point it at the remote session on the Exchange server.

Categories