Inserting a PowerShell command into c# - c#

I want to use this PowerShell command in a C# project:
Get-VM -Name Win8-Henry | Get-VMNetworkAdapter | Select MacAddress
This is what I normally do in c#:
public static void MacAdd(string machineName,Runspace run) {
// Get-VM -Name Win8-Henry | Get-VMNetworkAdapter | Select MacAddress
Command command = new Command("Get-VM");
command.Parameters.Add("Name", machineName);
using (Pipeline hostPipeline = run.CreatePipeline())
{
hostPipeline.Commands.Add(command);
Collection<PSObject> echos = hostPipeline.Invoke();
hostPipeline.Stop();
}
}
What I need help with is adding the second command, and then using the pipeline.

Use the AddScript() method on the PowerShell class.
var Command = String.Format("Get-VM -Name {0} | Get-VMNetworkAdapter | Select MacAddress", computername);
var PowerShell = PowerShell.Create();
PowerShell.AddScript(Command);
PowerShell.Invoke();

Just add the other commands to the pipeline too :)
hostPipeline.Commands.Add(new Command("Get-VMNetworkAdapter"));

Just to share what I do when I have a script that I need to execute in an application. I can then replace the parameters (i.e., "$!$machineName$!$" easily and get the result cleanly.
Dim lxScript As XElement = <Script>
.{
Get-VM -Name $!$machineName$!$ | Get-VMNetworkAdapter | Select MacAddress
}
</Script>

Related

How to pass a powershell script to Windows PowerShell Host in C#?

I would like to use the methods of Windows PowerShell Host on C# project (.NETFramework)
I had installed System.Management.Automation.dll on my project to run the commands of PowerShell on C#.
My goal is pass my ps1 file that contains:
$ProcessName = "Notepad"
$Path = "D:\FolderName\data.txt"
$CpuCores = (Get-WMIObject Win32_ComputerSystem).NumberOfLogicalProcessors
$Samples = (Get-Counter "\Process($Processname*)\% Processor Time").CounterSamples
$Samples | Select #{Name="CPU %";Expression={[Decimal]::Round(($_.CookedValue / $CpuCores), 2)}} | Out-File -FilePath $Path -Append
to a native implementation in C#. This return the CPU usage of a process.
I want to use the PowerShell Object to avoid to have the ps1 file, because I want to write the previous commands on C# using the System.Management.Automation.PowerShell class, example:
PowerShell powerShellCommand = PowerShell.Create();
powerShellCommand.AddCommand("Get-WMIObject");
powerShellCommand.AddArgument("Win32_ComputerSystem");
powerShellCommand.AddArgument("NumberOfLogicalProcessors ");
Do you have any idea how to transfer it to powershell Object and methods on C#?
You can add parameters block (param()) to your script and invoke it:
const string script = #"
param(
[string] $ProcessName,
[string] $Path
)
$CpuCores = (Get-WMIObject Win32_ComputerSystem).NumberOfLogicalProcessors
$Samples = (Get-Counter ""\Process($ProcessName*)\% Processor Time"").CounterSamples
$Samples |
Select #{Name=""CPU %"";Expression={[Decimal]::Round(($_.CookedValue / $CpuCores), 2)}} |
Out-File -FilePath $Path -Append";
PowerShell powerShellCommand = PowerShell.Create();
powerShellCommand
.AddScript(script)
.AddParameters(new PSPrimitiveDictionary
{
{ "ProcessName", "Notepad" },
{ "Path", #"D:\FolderName\data.txt" }
})
.Invoke();

C# Pipeline.Invoke() not returning powershell collection

On Windows 10, in C# I am trying to call a powershell script via Pipeline.Invoke() to retrieve all the disks on a network server, for each server on a network. But can't get it to return the data to C# when there are multiple disks on a server.
I have the following C# code:
foreach (var server in _collServers)
{
Pipeline pipeline2 = runspace.CreatePipeline();
pipeline2 = runspace.CreatePipeline();
scriptCommand = new Command(#"C:\DEV\Monitor\Monitor\bin\scripts\GetDisks.ps1");
CommandParameter commandParm = new CommandParameter("$server", server);
scriptCommand.Parameters.Add(commandParm);
pipeline2.Commands.Add(scriptCommand);
var collDisks = pipeline2.Invoke();
foreach (var item in collDisks)
{
var s = item.ToString();
}
}
which executes the following powershell script (GetDisks.ps1), with ComputerName hardcoded to a specific server for now:
$disks = Get-CimInstance -ClassName CIM_LogicalDisk -ComputerName SERVER01 | Where-Object { ($_.DeviceID -ge 'C') -and ($_.DriveType -eq 3)} | Select-Object DeviceID, VolumeName, Size, Freespace
$disksout = [System.Collections.Generic.List[PSObject]]::New()
ForEach($d in $disks)
{
$disk = [PSObject] #{
'DeviceID' = $d.DeviceID
'VolumeName' = $d.VolumeName
'Size' = $d.Size
'Freespace' = $d.Freespace
}
$disksout.Add($disk)
}
$disksout
but it doesnt return a PSObjects collection into collDisks.
It works in the ISE (and every re-arrangement I try seems to work there).
It works if I run it for my local machine by removing -ComputerName SERVER01
$disks = Get-CimInstance -ClassName CIM_LogicalDisk | Where-Object { ($_.DeviceID -ge 'C') -and ($_.DriveType -eq 3)} | Select-Object DeviceID, VolumeName, Size, Freespace
It doesnt make any difference if I try using PSCustomObject instead, or creating the object collection different ways.

.NET Core Powershell returns empty string when executing "Get-WmiObject"

I'm trying to execute this command:
Get-WmiObject -Class Win32_Product -Filter "Vendor = 'Microsoft'"
on .NET Core 3.1 app hosted on Windows
Here's my code:
var param = new Dictionary<string, object>();
param.Add("Class", "Win32_Product");
param.Add("Filter", "Vendor = 'Microsoft'");
var result = await ScriptHelper.RunScript("Get-WmiObject", param);
public static async Task<string> RunScript(string scriptContents, Dictionary<string, object> scriptParameters)
{
using (PowerShell ps = PowerShell.Create())
{
ps.AddScript(scriptContents);
ps.AddParameters(scriptParameters);
var pipelineObjects = await ps.InvokeAsync().ConfigureAwait(false);
var sb = new StringBuilder();
foreach (var item in pipelineObjects)
{
sb.AppendLine(item.BaseObject.ToString());
}
return sb.ToString();
}
}
But for some reason it returns an empty string instead of e.g
IdentifyingNumber : ...
Name : ...
Vendor : Microsoft
Version : ...
Caption : ...
I'm using
"Microsoft.PowerShell.SDK" Version="7.0.3"
Thanks in advance
I managed to obtain those informations via:
using System.Management;
var data = new ManagementObjectSearcher("SELECT * FROM Win32_Product WHERE Vendor = 'Microsoft'").Get();
foreach (var entry in data)
{
...
}
Is there a reason you're not using Get-CimInstance rather than Get-WmiObject?
If I use Get-CimInstance -ClassName Win32_Product -Filter "Vendor='Microsoft'" I get this result on Powershell Core 7.0.3:
Name Caption Vendor Version IdentifyingNumber
---- ------- ------ ------- -----------------
PowerToys (Prev… PowerToys (Preview) Microsoft 0.19.2 {3EFDE709-F7B5-4AC9-8263-80D…
while I get the following error message using Get-WmiObject:
Get-WmiObject: The term 'Get-WmiObject' 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.
Update:
If you use -Filter without wildcards it expects an exact result which means your original cmdlet does not return anything if the value of Vendor is the standard Microsoft Corporation rather than just Microsoft.
It seems that the -Filter and -Query parameters are somewhat iffy on Get-Ciminstance.
I first ran a simple Get-CimInstance -ClassName Win32_Product and got back a ton of results with among others Microsoft Corporation.
Then I ran both:
Get-CimInstance -ClassName Win32_Product -Filter "Vendor like 'Microsoft*'"
and
Get-CimInstance -Query "SELECT * FROM Win32_Product WHERE Vendor LIKE 'Microsoft*'"
And got nothing back though they should support wild cards.
What worked for me was this:
Get-CimInstance -ClassName Win32_Product | Where-Object {$_.Vendor -like 'Microsoft*'}

How to run Powershell file with ODP dependency in background of C# console app?

I've one PowerShell script file which contains ODP assembly to connect to oracle database. I want to run it in the background of C# console application then display the result in the C# console. This is the PowerShell script:-
Add-Type -path "C:\path\Oracle.DataAccess.dll"
$constr = "User Id=userxxx;Password=xxxx;Data Source=xxxx"
$conn= New-Object Oracle.DataAccess.Client.OracleConnection($constr)
$conn.Open()
$sql="SELECT A,B,C,D FROM XXXXX"
$command = New-Object Oracle.DataAccess.Client.OracleCommand( $sql,$conn)
$reader=$command.ExecuteReader()
$columnNames=$reader.GetSchemaTable() | Select-Object -ExpandProperty ColumnName
$resultSet=#()
while ($reader.Read()) {
$result=New-Object object
$result | Add-Member -NotePropertyName $columnNames[0] -NotePropertyValue $reader.GetDateTime(0)
$result | Add-Member -NotePropertyName $columnNames[1] -NotePropertyValue $reader.GetDateTime(1)
$result | Add-Member -NotePropertyName $columnNames[2] -NotePropertyValue $reader.GetDateTime(2)
$result | Add-Member -NotePropertyName $columnNames[3] -NotePropertyValue $reader.GetDateTime(3)
$resultSet += $result
}
$conn.Close()
$resultSet | Format-Table -AutoSize
Read-Host -Prompt “Press Enter to exit”
I tried this code using C# to use the PowerShell script:-
RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
runspace.Open();
RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
Pipeline pipeline = runspace.CreatePipeline();
Command myCommand = new Command(#"C:\path\connectDB.ps1");
pipeline.Commands.Add(myCommand);
pipeline.Commands.Add("Out-String");
Collection<PSObject> results = pipeline.Invoke();
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
Console.WriteLine(stringBuilder.ToString());
Console.ReadLine();
But I got this exception: An unhandled exception of type 'System.Management.Automation.CmdletInvocationException' occurred in System.Management.Automation.dll
And this is the message: Additional information: Could not load file or assembly 'file:///C:\path\Oracle.DataAccess.dll' or one of its dependencies. An attempt was made to load a program with an program with an incorrect format.
How can I view the data, PowerShell file name, username and database used for this in the C# console screen?
There's no reason to use Powershell at all. The Powershell script uses ADO.NET objects which are easier to use in C#. The documentation of the OracleDataReader has an example that shows how to execute a query and read the results.
public void ReadData(string connectionString)
{
string queryString = "SELECT A,B,C,D FROM XXXXX";
using (var connection = new OracleConnection(connectionString))
{
var command = new OracleCommand(queryString, connection);
connection.Open();
using(OracleDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine("{0}, {1}, {2}, {3}",
reader.GetDateTime(0),
reader.GetDateTime(1),
reader.GetDateTime(2),
reader.GetDateTime(3));
}
}
}
}
The question doesn't explain how the results will be used. Most likely, they won't be printed out.
One option is to use them in the loop itself. Another option is to load them in a DataTable by calling DataTable.Load(), eg
using(OracleDataReader reader = command.ExecuteReader())
{
var table=new DataTable();
table.Load(reader);
return table;
}
The DataTable will contain the columns and types returned by the reader.
ADO.NET was part of .NET since the beginning so it's very well covered in the documentation, courses, articles and tutorials.
Nowadays, it's much more common to load results directly into strongly typed objects using ORMs like Entity Framework or micro-ORMs like Dapper. Assuming we have this class :
class MyResultClass
{
public DateTime A {get;set;}
public DateTime B {get;set;}
public DateTime C {get;set;}
public DateTime D {get;set;}
}
Dapper allows us to load a list of results with a simple :
string queryString = "SELECT A,B,C,D FROM XXXXX";
using (var connection = new OracleConnection(connectionString))
{
var results=connection.Query<MyResultClass>(queryString);
return results;
}

Calling PowerShell's where-object from C#

How can I call Where-Object (without filter) from C#? I can't parse output, because I want to pass it to pipeline(not in example below).
PS:
Get-MailboxPermission username | Where-Object {$_.AccessRights -match "FullAccess" -and $_.IsInherited -eq $False}
C#:
Collection<PSObject> results = null;
Command command1 = new Command("Get-MailboxPermission");
command1.Parameters.Add("Identity", mailbox);
command1.Parameters.Add("DomainController", _domainController);
//I cannot use Filter. This is not possible in PS also.
//command1.Parameters.Add("Filter", "AccessRights -match \"FullAccess\"");
This question is simmilar to: PowerShell WhereObjectCommand from C# That answer is not enough for my problem.
Check the below code sample. I have updated a sample from code project here as per your requirements.
Note:
To add quotes to your command i.e. script text escape quotes by using \"
To add { or } brace to the script text use double curly braces instead to escape it inside String.Format like {{ or }}
// create Powershell runspace
Runspace runspace = RunspaceFactory.CreateRunspace();
// open it
runspace.Open();
// create a pipeline and feed it the script text
Pipeline pipeline = runspace.CreatePipeline();
//Getting all command variables
string computerName = "YourComputerName";
string matchingPattern = "con";
//Create Script command
String customScriptText = String.Format("Get-Process -ComputerName {0} | Where-Object {{$_.ProcessName -match \"{1}\"}}", computerName,matchingPattern);
pipeline.Commands.AddScript(customScriptText);
// add an extra command to transform the script output objects into nicely formatted strings
// remove this line to get the actual objects that the script returns.
pipeline.Commands.Add("Out-String");
// execute the script
Collection<PSObject> results = pipeline.Invoke();
// close the runspace
runspace.Close();
// convert the script result into a single string
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
When you apply the above working sample to your problem you will need to change couple of lines like below:
//Getting all command variables
string username = "YourUserName";
//Create Script command
String customScriptText = String.Format("Get-MailboxPermission {0} | Where-Object {{$_.AccessRights -match \"FullAccess\" -and $_.IsInherited -eq $False}}", username);

Categories

Resources